parent
9612a41f5a
commit
5ab19ea469
|
@ -118,9 +118,16 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
|
||||||
err := session.Wait()
|
err := session.Wait()
|
||||||
exitStatus := 0
|
exitStatus := 0
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exitErr, ok := err.(*ssh.ExitError)
|
switch err.(type) {
|
||||||
if ok {
|
case *ssh.ExitError:
|
||||||
exitStatus = exitErr.ExitStatus()
|
exitStatus = err.(*ssh.ExitError).ExitStatus()
|
||||||
|
log.Printf("remote command exited with '%d': %s", exitStatus, cmd.Command)
|
||||||
|
case *ssh.ExitMissingError:
|
||||||
|
log.Printf(err.Error())
|
||||||
|
exitStatus = -1
|
||||||
|
default:
|
||||||
|
log.Printf("unknown error occurred waiting for ssh session.")
|
||||||
|
exitStatus = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
// 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 ed25519 implements the Ed25519 signature algorithm. See
|
||||||
|
// http://ed25519.cr.yp.to/.
|
||||||
|
//
|
||||||
|
// These functions are also compatible with the “Ed25519” function defined in
|
||||||
|
// https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05.
|
||||||
|
package ed25519
|
||||||
|
|
||||||
|
// This code is a port of the public domain, “ref10” implementation of ed25519
|
||||||
|
// from SUPERCOP.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
cryptorand "crypto/rand"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519/internal/edwards25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||||
|
PublicKeySize = 32
|
||||||
|
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||||
|
PrivateKeySize = 64
|
||||||
|
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||||
|
SignatureSize = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicKey is the type of Ed25519 public keys.
|
||||||
|
type PublicKey []byte
|
||||||
|
|
||||||
|
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
|
||||||
|
type PrivateKey []byte
|
||||||
|
|
||||||
|
// Public returns the PublicKey corresponding to priv.
|
||||||
|
func (priv PrivateKey) Public() crypto.PublicKey {
|
||||||
|
publicKey := make([]byte, PublicKeySize)
|
||||||
|
copy(publicKey, priv[32:])
|
||||||
|
return PublicKey(publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the given message with priv.
|
||||||
|
// Ed25519 performs two passes over messages to be signed and therefore cannot
|
||||||
|
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
|
||||||
|
// indicate the message hasn't been hashed. This can be achieved by passing
|
||||||
|
// crypto.Hash(0) as the value for opts.
|
||||||
|
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
if opts.HashFunc() != crypto.Hash(0) {
|
||||||
|
return nil, errors.New("ed25519: cannot sign hashed message")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sign(priv, message), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||||
|
// If rand is nil, crypto/rand.Reader will be used.
|
||||||
|
func GenerateKey(rand io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error) {
|
||||||
|
if rand == nil {
|
||||||
|
rand = cryptorand.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey = make([]byte, PrivateKeySize)
|
||||||
|
publicKey = make([]byte, PublicKeySize)
|
||||||
|
_, err = io.ReadFull(rand, privateKey[:32])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := sha512.Sum512(privateKey[:32])
|
||||||
|
digest[0] &= 248
|
||||||
|
digest[31] &= 127
|
||||||
|
digest[31] |= 64
|
||||||
|
|
||||||
|
var A edwards25519.ExtendedGroupElement
|
||||||
|
var hBytes [32]byte
|
||||||
|
copy(hBytes[:], digest[:])
|
||||||
|
edwards25519.GeScalarMultBase(&A, &hBytes)
|
||||||
|
var publicKeyBytes [32]byte
|
||||||
|
A.ToBytes(&publicKeyBytes)
|
||||||
|
|
||||||
|
copy(privateKey[32:], publicKeyBytes[:])
|
||||||
|
copy(publicKey, publicKeyBytes[:])
|
||||||
|
|
||||||
|
return publicKey, privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the message with privateKey and returns a signature. It will
|
||||||
|
// panic if len(privateKey) is not PrivateKeySize.
|
||||||
|
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||||
|
if l := len(privateKey); l != PrivateKeySize {
|
||||||
|
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write(privateKey[:32])
|
||||||
|
|
||||||
|
var digest1, messageDigest, hramDigest [64]byte
|
||||||
|
var expandedSecretKey [32]byte
|
||||||
|
h.Sum(digest1[:0])
|
||||||
|
copy(expandedSecretKey[:], digest1[:])
|
||||||
|
expandedSecretKey[0] &= 248
|
||||||
|
expandedSecretKey[31] &= 63
|
||||||
|
expandedSecretKey[31] |= 64
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(digest1[32:])
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(messageDigest[:0])
|
||||||
|
|
||||||
|
var messageDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
||||||
|
var R edwards25519.ExtendedGroupElement
|
||||||
|
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
||||||
|
|
||||||
|
var encodedR [32]byte
|
||||||
|
R.ToBytes(&encodedR)
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(encodedR[:])
|
||||||
|
h.Write(privateKey[32:])
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(hramDigest[:0])
|
||||||
|
var hramDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
||||||
|
|
||||||
|
var s [32]byte
|
||||||
|
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
|
||||||
|
|
||||||
|
signature := make([]byte, SignatureSize)
|
||||||
|
copy(signature[:], encodedR[:])
|
||||||
|
copy(signature[32:], s[:])
|
||||||
|
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||||
|
// will panic if len(publicKey) is not PublicKeySize.
|
||||||
|
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||||
|
if l := len(publicKey); l != PublicKeySize {
|
||||||
|
panic("ed25519: bad public key length: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sig) != SignatureSize || sig[63]&224 != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var A edwards25519.ExtendedGroupElement
|
||||||
|
var publicKeyBytes [32]byte
|
||||||
|
copy(publicKeyBytes[:], publicKey)
|
||||||
|
if !A.FromBytes(&publicKeyBytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
edwards25519.FeNeg(&A.X, &A.X)
|
||||||
|
edwards25519.FeNeg(&A.T, &A.T)
|
||||||
|
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write(sig[:32])
|
||||||
|
h.Write(publicKey[:])
|
||||||
|
h.Write(message)
|
||||||
|
var digest [64]byte
|
||||||
|
h.Sum(digest[:0])
|
||||||
|
|
||||||
|
var hReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&hReduced, &digest)
|
||||||
|
|
||||||
|
var R edwards25519.ProjectiveGroupElement
|
||||||
|
var b [32]byte
|
||||||
|
copy(b[:], sig[32:])
|
||||||
|
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
|
||||||
|
|
||||||
|
var checkR [32]byte
|
||||||
|
R.ToBytes(&checkR)
|
||||||
|
return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1
|
||||||
|
}
|
1422
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go
generated
vendored
Normal file
1422
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1771
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go
generated
vendored
Normal file
1771
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -2,12 +2,13 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
/*
|
// Package agent implements the ssh-agent protocol, and provides both
|
||||||
Package agent implements a client to an ssh-agent daemon.
|
// a client and a server. The client can talk to a standard ssh-agent
|
||||||
|
// that uses UNIX sockets, and one could implement an alternative
|
||||||
References:
|
// ssh-agent process using the sample server.
|
||||||
[PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
|
//
|
||||||
*/
|
// References:
|
||||||
|
// [PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
|
||||||
package agent // import "golang.org/x/crypto/ssh/agent"
|
package agent // import "golang.org/x/crypto/ssh/agent"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -24,6 +25,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,7 +77,8 @@ type AddedKey struct {
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 3.
|
// See [PROTOCOL.agent], section 3.
|
||||||
const (
|
const (
|
||||||
agentRequestV1Identities = 1
|
agentRequestV1Identities = 1
|
||||||
|
agentRemoveAllV1Identities = 9
|
||||||
|
|
||||||
// 3.2 Requests from client to agent for protocol 2 key operations
|
// 3.2 Requests from client to agent for protocol 2 key operations
|
||||||
agentAddIdentity = 17
|
agentAddIdentity = 17
|
||||||
|
@ -182,10 +185,13 @@ func (k *Key) Marshal() []byte {
|
||||||
return k.Blob
|
return k.Blob
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify satisfies the ssh.PublicKey interface, but is not
|
// Verify satisfies the ssh.PublicKey interface.
|
||||||
// implemented for agent keys.
|
|
||||||
func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
|
func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
|
||||||
return errors.New("agent: agent key does not know how to verify")
|
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 {
|
type wireKey struct {
|
||||||
|
@ -375,6 +381,8 @@ func unmarshal(packet []byte) (interface{}, error) {
|
||||||
msg = new(identitiesAnswerAgentMsg)
|
msg = new(identitiesAnswerAgentMsg)
|
||||||
case agentSignResponse:
|
case agentSignResponse:
|
||||||
msg = new(signResponseAgentMsg)
|
msg = new(signResponseAgentMsg)
|
||||||
|
case agentV1IdentitiesAnswer:
|
||||||
|
msg = new(agentV1IdentityMsg)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("agent: unknown type tag %d", packet[0])
|
return nil, fmt.Errorf("agent: unknown type tag %d", packet[0])
|
||||||
}
|
}
|
||||||
|
@ -385,7 +393,7 @@ func unmarshal(packet []byte) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type rsaKeyMsg struct {
|
type rsaKeyMsg struct {
|
||||||
Type string `sshtype:"17"`
|
Type string `sshtype:"17|25"`
|
||||||
N *big.Int
|
N *big.Int
|
||||||
E *big.Int
|
E *big.Int
|
||||||
D *big.Int
|
D *big.Int
|
||||||
|
@ -397,7 +405,7 @@ type rsaKeyMsg struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type dsaKeyMsg struct {
|
type dsaKeyMsg struct {
|
||||||
Type string `sshtype:"17"`
|
Type string `sshtype:"17|25"`
|
||||||
P *big.Int
|
P *big.Int
|
||||||
Q *big.Int
|
Q *big.Int
|
||||||
G *big.Int
|
G *big.Int
|
||||||
|
@ -408,7 +416,7 @@ type dsaKeyMsg struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ecdsaKeyMsg struct {
|
type ecdsaKeyMsg struct {
|
||||||
Type string `sshtype:"17"`
|
Type string `sshtype:"17|25"`
|
||||||
Curve string
|
Curve string
|
||||||
KeyBytes []byte
|
KeyBytes []byte
|
||||||
D *big.Int
|
D *big.Int
|
||||||
|
@ -416,6 +424,14 @@ type ecdsaKeyMsg struct {
|
||||||
Constraints []byte `ssh:"rest"`
|
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.
|
// Insert adds a private key to the agent.
|
||||||
func (c *client) insertKey(s interface{}, comment string, constraints []byte) error {
|
func (c *client) insertKey(s interface{}, comment string, constraints []byte) error {
|
||||||
var req []byte
|
var req []byte
|
||||||
|
@ -457,6 +473,14 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
|
||||||
Comments: comment,
|
Comments: comment,
|
||||||
Constraints: constraints,
|
Constraints: constraints,
|
||||||
})
|
})
|
||||||
|
case *ed25519.PrivateKey:
|
||||||
|
req = ssh.Marshal(ed25519KeyMsg{
|
||||||
|
Type: ssh.KeyAlgoED25519,
|
||||||
|
Pub: []byte(*k)[32:],
|
||||||
|
Priv: []byte(*k),
|
||||||
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||||
}
|
}
|
||||||
|
@ -477,7 +501,7 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
|
||||||
}
|
}
|
||||||
|
|
||||||
type rsaCertMsg struct {
|
type rsaCertMsg struct {
|
||||||
Type string `sshtype:"17"`
|
Type string `sshtype:"17|25"`
|
||||||
CertBytes []byte
|
CertBytes []byte
|
||||||
D *big.Int
|
D *big.Int
|
||||||
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
||||||
|
@ -488,7 +512,7 @@ type rsaCertMsg struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type dsaCertMsg struct {
|
type dsaCertMsg struct {
|
||||||
Type string `sshtype:"17"`
|
Type string `sshtype:"17|25"`
|
||||||
CertBytes []byte
|
CertBytes []byte
|
||||||
X *big.Int
|
X *big.Int
|
||||||
Comments string
|
Comments string
|
||||||
|
@ -496,14 +520,23 @@ type dsaCertMsg struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ecdsaCertMsg struct {
|
type ecdsaCertMsg struct {
|
||||||
Type string `sshtype:"17"`
|
Type string `sshtype:"17|25"`
|
||||||
CertBytes []byte
|
CertBytes []byte
|
||||||
D *big.Int
|
D *big.Int
|
||||||
Comments string
|
Comments string
|
||||||
Constraints []byte `ssh:"rest"`
|
Constraints []byte `ssh:"rest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert adds a private key to the agent. If a certificate is given,
|
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.
|
// that certificate is added instead as public key.
|
||||||
func (c *client) Add(key AddedKey) error {
|
func (c *client) Add(key AddedKey) error {
|
||||||
var constraints []byte
|
var constraints []byte
|
||||||
|
@ -547,17 +580,28 @@ func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string
|
||||||
})
|
})
|
||||||
case *dsa.PrivateKey:
|
case *dsa.PrivateKey:
|
||||||
req = ssh.Marshal(dsaCertMsg{
|
req = ssh.Marshal(dsaCertMsg{
|
||||||
Type: cert.Type(),
|
Type: cert.Type(),
|
||||||
CertBytes: cert.Marshal(),
|
CertBytes: cert.Marshal(),
|
||||||
X: k.X,
|
X: k.X,
|
||||||
Comments: comment,
|
Comments: comment,
|
||||||
|
Constraints: constraints,
|
||||||
})
|
})
|
||||||
case *ecdsa.PrivateKey:
|
case *ecdsa.PrivateKey:
|
||||||
req = ssh.Marshal(ecdsaCertMsg{
|
req = ssh.Marshal(ecdsaCertMsg{
|
||||||
Type: cert.Type(),
|
Type: cert.Type(),
|
||||||
CertBytes: cert.Marshal(),
|
CertBytes: cert.Marshal(),
|
||||||
D: k.D,
|
D: k.D,
|
||||||
Comments: comment,
|
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:
|
default:
|
||||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
@ -18,6 +19,7 @@ import (
|
||||||
type privKey struct {
|
type privKey struct {
|
||||||
signer ssh.Signer
|
signer ssh.Signer
|
||||||
comment string
|
comment string
|
||||||
|
expire *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyring struct {
|
type keyring struct {
|
||||||
|
@ -48,15 +50,9 @@ func (r *keyring) RemoveAll() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes all identities with the given public key.
|
// removeLocked does the actual key removal. The caller must already be holding the
|
||||||
func (r *keyring) Remove(key ssh.PublicKey) error {
|
// keyring mutex.
|
||||||
r.mu.Lock()
|
func (r *keyring) removeLocked(want []byte) error {
|
||||||
defer r.mu.Unlock()
|
|
||||||
if r.locked {
|
|
||||||
return errLocked
|
|
||||||
}
|
|
||||||
|
|
||||||
want := key.Marshal()
|
|
||||||
found := false
|
found := false
|
||||||
for i := 0; i < len(r.keys); {
|
for i := 0; i < len(r.keys); {
|
||||||
if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
|
if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
|
||||||
|
@ -75,7 +71,18 @@ func (r *keyring) Remove(key ssh.PublicKey) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
|
// 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 {
|
func (r *keyring) Lock(passphrase []byte) error {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
@ -104,6 +111,17 @@ func (r *keyring) Unlock(passphrase []byte) error {
|
||||||
return 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.
|
// List returns the identities known to the agent.
|
||||||
func (r *keyring) List() ([]*Key, error) {
|
func (r *keyring) List() ([]*Key, error) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
|
@ -113,6 +131,7 @@ func (r *keyring) List() ([]*Key, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.expireKeysLocked()
|
||||||
var ids []*Key
|
var ids []*Key
|
||||||
for _, k := range r.keys {
|
for _, k := range r.keys {
|
||||||
pub := k.signer.PublicKey()
|
pub := k.signer.PublicKey()
|
||||||
|
@ -146,7 +165,17 @@ func (r *keyring) Add(key AddedKey) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.keys = append(r.keys, privKey{signer, key.Comment})
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -159,6 +188,7 @@ func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||||
return nil, errLocked
|
return nil, errLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.expireKeysLocked()
|
||||||
wanted := key.Marshal()
|
wanted := key.Marshal()
|
||||||
for _, k := range r.keys {
|
for _, k := range r.keys {
|
||||||
if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
|
if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
|
||||||
|
@ -176,6 +206,7 @@ func (r *keyring) Signers() ([]ssh.Signer, error) {
|
||||||
return nil, errLocked
|
return nil, errLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.expireKeysLocked()
|
||||||
s := make([]ssh.Signer, 0, len(r.keys))
|
s := make([]ssh.Signer, 0, len(r.keys))
|
||||||
for _, k := range r.keys {
|
for _, k := range r.keys {
|
||||||
s = append(s, k.signer)
|
s = append(s, k.signer)
|
||||||
|
|
|
@ -5,13 +5,18 @@
|
||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/dsa"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,6 +54,9 @@ func marshalKey(k *Key) []byte {
|
||||||
return ssh.Marshal(&record)
|
return ssh.Marshal(&record)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See [PROTOCOL.agent], section 2.5.1.
|
||||||
|
const agentV1IdentitiesAnswer = 2
|
||||||
|
|
||||||
type agentV1IdentityMsg struct {
|
type agentV1IdentityMsg struct {
|
||||||
Numkeys uint32 `sshtype:"2"`
|
Numkeys uint32 `sshtype:"2"`
|
||||||
}
|
}
|
||||||
|
@ -69,6 +77,10 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
|
||||||
switch data[0] {
|
switch data[0] {
|
||||||
case agentRequestV1Identities:
|
case agentRequestV1Identities:
|
||||||
return &agentV1IdentityMsg{0}, nil
|
return &agentV1IdentityMsg{0}, nil
|
||||||
|
|
||||||
|
case agentRemoveAllV1Identities:
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
case agentRemoveIdentity:
|
case agentRemoveIdentity:
|
||||||
var req agentRemoveIdentityMsg
|
var req agentRemoveIdentityMsg
|
||||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||||
|
@ -121,6 +133,7 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
|
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
|
||||||
|
|
||||||
case agentRequestIdentities:
|
case agentRequestIdentities:
|
||||||
keys, err := s.agent.List()
|
keys, err := s.agent.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -134,42 +147,271 @@ func (s *server) processRequest(data []byte) (interface{}, error) {
|
||||||
rep.Keys = append(rep.Keys, marshalKey(k)...)
|
rep.Keys = append(rep.Keys, marshalKey(k)...)
|
||||||
}
|
}
|
||||||
return rep, nil
|
return rep, nil
|
||||||
case agentAddIdentity:
|
|
||||||
|
case agentAddIdConstrained, agentAddIdentity:
|
||||||
return nil, s.insertIdentity(data)
|
return nil, s.insertIdentity(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown opcode %d", data[0])
|
return nil, fmt.Errorf("unknown opcode %d", data[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, 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)
|
||||||
|
return &AddedKey{PrivateKey: &priv, Comment: k.Comments}, 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, 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")
|
||||||
|
}
|
||||||
|
return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AddedKey{PrivateKey: priv, Comment: k.Comments}, 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()
|
||||||
|
|
||||||
|
return &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}, 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *server) insertIdentity(req []byte) error {
|
func (s *server) insertIdentity(req []byte) error {
|
||||||
var record struct {
|
var record struct {
|
||||||
Type string `sshtype:"17"`
|
Type string `sshtype:"17|25"`
|
||||||
Rest []byte `ssh:"rest"`
|
Rest []byte `ssh:"rest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ssh.Unmarshal(req, &record); err != nil {
|
if err := ssh.Unmarshal(req, &record); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var addedKey *AddedKey
|
||||||
|
var err error
|
||||||
|
|
||||||
switch record.Type {
|
switch record.Type {
|
||||||
case ssh.KeyAlgoRSA:
|
case ssh.KeyAlgoRSA:
|
||||||
var k rsaKeyMsg
|
addedKey, err = parseRSAKey(req)
|
||||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
case ssh.KeyAlgoDSA:
|
||||||
return err
|
addedKey, err = parseDSAKey(req)
|
||||||
}
|
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
|
||||||
|
addedKey, err = parseECDSAKey(req)
|
||||||
priv := rsa.PrivateKey{
|
case ssh.KeyAlgoED25519:
|
||||||
PublicKey: rsa.PublicKey{
|
addedKey, err = parseEd25519Key(req)
|
||||||
E: int(k.E.Int64()),
|
case ssh.CertAlgoRSAv01:
|
||||||
N: k.N,
|
addedKey, err = parseRSACert(req)
|
||||||
},
|
case ssh.CertAlgoDSAv01:
|
||||||
D: k.D,
|
addedKey, err = parseDSACert(req)
|
||||||
Primes: []*big.Int{k.P, k.Q},
|
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
|
||||||
}
|
addedKey, err = parseECDSACert(req)
|
||||||
priv.Precompute()
|
case ssh.CertAlgoED25519v01:
|
||||||
|
addedKey, err = parseEd25519Cert(req)
|
||||||
return s.agent.Add(AddedKey{PrivateKey: &priv, Comment: k.Comments})
|
default:
|
||||||
|
return fmt.Errorf("agent: not implemented: %q", record.Type)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("not implemented: %s", record.Type)
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.agent.Add(*addedKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeAgent serves the agent protocol on the given connection. It
|
// ServeAgent serves the agent protocol on the given connection. It
|
||||||
|
|
|
@ -22,6 +22,7 @@ const (
|
||||||
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
|
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
|
||||||
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
|
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
|
||||||
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-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
|
// Certificate types distinguish between host and user
|
||||||
|
@ -401,6 +402,7 @@ var certAlgoNames = map[string]string{
|
||||||
KeyAlgoECDSA256: CertAlgoECDSA256v01,
|
KeyAlgoECDSA256: CertAlgoECDSA256v01,
|
||||||
KeyAlgoECDSA384: CertAlgoECDSA384v01,
|
KeyAlgoECDSA384: CertAlgoECDSA384v01,
|
||||||
KeyAlgoECDSA521: CertAlgoECDSA521v01,
|
KeyAlgoECDSA521: CertAlgoECDSA521v01,
|
||||||
|
KeyAlgoED25519: CertAlgoED25519v01,
|
||||||
}
|
}
|
||||||
|
|
||||||
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
|
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
|
||||||
|
@ -459,7 +461,7 @@ func (c *Certificate) Marshal() []byte {
|
||||||
func (c *Certificate) Type() string {
|
func (c *Certificate) Type() string {
|
||||||
algo, ok := certAlgoNames[c.Key.Type()]
|
algo, ok := certAlgoNames[c.Key.Type()]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("unknown cert key type")
|
panic("unknown cert key type " + c.Key.Type())
|
||||||
}
|
}
|
||||||
return algo
|
return algo
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,8 @@ type Channel interface {
|
||||||
// boolean, otherwise the return value will be false. Channel
|
// boolean, otherwise the return value will be false. Channel
|
||||||
// requests are out-of-band messages so they may be sent even
|
// 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 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)
|
SendRequest(name string, wantReply bool, payload []byte) (bool, error)
|
||||||
|
|
||||||
// Stderr returns an io.ReadWriter that writes to this channel
|
// Stderr returns an io.ReadWriter that writes to this channel
|
||||||
|
@ -217,7 +219,7 @@ func (c *channel) writePacket(packet []byte) error {
|
||||||
|
|
||||||
func (c *channel) sendMessage(msg interface{}) error {
|
func (c *channel) sendMessage(msg interface{}) error {
|
||||||
if debugMux {
|
if debugMux {
|
||||||
log.Printf("send %d: %#v", c.mux.chanList.offset, msg)
|
log.Printf("send(%d): %#v", c.mux.chanList.offset, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := Marshal(msg)
|
p := Marshal(msg)
|
||||||
|
@ -371,7 +373,7 @@ func (c *channel) close() {
|
||||||
close(c.msg)
|
close(c.msg)
|
||||||
close(c.incomingRequests)
|
close(c.incomingRequests)
|
||||||
c.writeMu.Lock()
|
c.writeMu.Lock()
|
||||||
// This is not necesary for a normal channel teardown, but if
|
// This is not necessary for a normal channel teardown, but if
|
||||||
// there was another error, it is.
|
// there was another error, it is.
|
||||||
c.sentClose = true
|
c.sentClose = true
|
||||||
c.writeMu.Unlock()
|
c.writeMu.Unlock()
|
||||||
|
|
|
@ -7,6 +7,7 @@ package ssh
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"crypto/des"
|
||||||
"crypto/rc4"
|
"crypto/rc4"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
@ -121,6 +122,9 @@ var cipherModes = map[string]*streamCipherMode{
|
||||||
// You should expect that an active attacker can recover plaintext if
|
// You should expect that an active attacker can recover plaintext if
|
||||||
// you do.
|
// you do.
|
||||||
aes128cbcID: {16, aes.BlockSize, 0, nil},
|
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
|
// prefixLen is the length of the packet prefix that contains the packet length
|
||||||
|
@ -368,12 +372,7 @@ type cbcCipher struct {
|
||||||
oracleCamouflage uint32
|
oracleCamouflage uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
c, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cbc := &cbcCipher{
|
cbc := &cbcCipher{
|
||||||
mac: macModes[algs.MAC].new(macKey),
|
mac: macModes[algs.MAC].new(macKey),
|
||||||
decrypter: cipher.NewCBCDecrypter(c, iv),
|
decrypter: cipher.NewCBCDecrypter(c, iv),
|
||||||
|
@ -387,6 +386,34 @@ func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCi
|
||||||
return cbc, nil
|
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 {
|
func maxUInt32(a, b int) uint32 {
|
||||||
if a > b {
|
if a > b {
|
||||||
return uint32(a)
|
return uint32(a)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client implements a traditional SSH client that supports shells,
|
// Client implements a traditional SSH client that supports shells,
|
||||||
|
@ -96,16 +97,10 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e
|
||||||
c.transport = newClientTransport(
|
c.transport = newClientTransport(
|
||||||
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
|
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
|
||||||
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
|
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
|
||||||
if err := c.transport.requestKeyChange(); err != nil {
|
if err := c.transport.requestInitialKeyChange(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if packet, err := c.transport.readPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if packet[0] != msgNewKeys {
|
|
||||||
return unexpectedMessageError(msgNewKeys, packet[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// We just did the key change, so the session ID is established.
|
// We just did the key change, so the session ID is established.
|
||||||
c.sessionID = c.transport.getSessionID()
|
c.sessionID = c.transport.getSessionID()
|
||||||
|
|
||||||
|
@ -169,7 +164,7 @@ func (c *Client) handleChannelOpens(in <-chan NewChannel) {
|
||||||
// to incoming channels and requests, use net.Dial with NewClientConn
|
// to incoming channels and requests, use net.Dial with NewClientConn
|
||||||
// instead.
|
// instead.
|
||||||
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
||||||
conn, err := net.Dial(network, addr)
|
conn, err := net.DialTimeout(network, addr, config.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -210,4 +205,9 @@ type ClientConfig struct {
|
||||||
// string returned from PublicKey.Type method may be used, or
|
// string returned from PublicKey.Type method may be used, or
|
||||||
// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
|
// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
|
||||||
HostKeyAlgorithms []string
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -321,8 +321,6 @@ func handleAuthResponse(c packetConn) (bool, []string, error) {
|
||||||
return false, msg.Methods, nil
|
return false, msg.Methods, nil
|
||||||
case msgUserAuthSuccess:
|
case msgUserAuthSuccess:
|
||||||
return true, nil, nil
|
return true, nil, nil
|
||||||
case msgDisconnect:
|
|
||||||
return false, nil, io.EOF
|
|
||||||
default:
|
default:
|
||||||
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
||||||
}
|
}
|
||||||
|
@ -439,3 +437,37 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
|
|
@ -44,10 +44,12 @@ var supportedKexAlgos = []string{
|
||||||
// of authenticating servers) in preference order.
|
// of authenticating servers) in preference order.
|
||||||
var supportedHostKeyAlgos = []string{
|
var supportedHostKeyAlgos = []string{
|
||||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
||||||
CertAlgoECDSA384v01, CertAlgoECDSA521v01,
|
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
|
||||||
|
|
||||||
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
||||||
KeyAlgoRSA, KeyAlgoDSA,
|
KeyAlgoRSA, KeyAlgoDSA,
|
||||||
|
|
||||||
|
KeyAlgoED25519,
|
||||||
}
|
}
|
||||||
|
|
||||||
// supportedMACs specifies a default set of MAC algorithms in preference order.
|
// supportedMACs specifies a default set of MAC algorithms in preference order.
|
||||||
|
|
|
@ -23,7 +23,6 @@ func (e *OpenChannelError) Error() string {
|
||||||
// ConnMetadata holds metadata for the connection.
|
// ConnMetadata holds metadata for the connection.
|
||||||
type ConnMetadata interface {
|
type ConnMetadata interface {
|
||||||
// User returns the user ID for this connection.
|
// User returns the user ID for this connection.
|
||||||
// It is empty if no authentication is used.
|
|
||||||
User() string
|
User() string
|
||||||
|
|
||||||
// SessionID returns the sesson hash, also denoted by H.
|
// SessionID returns the sesson hash, also denoted by H.
|
||||||
|
|
|
@ -29,25 +29,6 @@ type keyingTransport interface {
|
||||||
// direction will be effected if a msgNewKeys message is sent
|
// direction will be effected if a msgNewKeys message is sent
|
||||||
// or received.
|
// or received.
|
||||||
prepareKeyChange(*algorithms, *kexResult) error
|
prepareKeyChange(*algorithms, *kexResult) error
|
||||||
|
|
||||||
// getSessionID returns the session ID. prepareKeyChange must
|
|
||||||
// have been called once.
|
|
||||||
getSessionID() []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// rekeyingTransport is the interface of handshakeTransport that we
|
|
||||||
// (internally) expose to ClientConn and ServerConn.
|
|
||||||
type rekeyingTransport interface {
|
|
||||||
packetConn
|
|
||||||
|
|
||||||
// requestKeyChange asks the remote side to change keys. All
|
|
||||||
// writes are blocked until the key change succeeds, which is
|
|
||||||
// signaled by reading a msgNewKeys.
|
|
||||||
requestKeyChange() error
|
|
||||||
|
|
||||||
// getSessionID returns the session ID. This is only valid
|
|
||||||
// after the first key change has completed.
|
|
||||||
getSessionID() []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handshakeTransport implements rekeying on top of a keyingTransport
|
// handshakeTransport implements rekeying on top of a keyingTransport
|
||||||
|
@ -86,6 +67,9 @@ type handshakeTransport struct {
|
||||||
sentInitMsg *kexInitMsg
|
sentInitMsg *kexInitMsg
|
||||||
writtenSinceKex uint64
|
writtenSinceKex uint64
|
||||||
writeError error
|
writeError error
|
||||||
|
|
||||||
|
// The session ID or nil if first kex did not complete yet.
|
||||||
|
sessionID []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport {
|
func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport {
|
||||||
|
@ -122,7 +106,7 @@ func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *handshakeTransport) getSessionID() []byte {
|
func (t *handshakeTransport) getSessionID() []byte {
|
||||||
return t.conn.getSessionID()
|
return t.sessionID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *handshakeTransport) id() string {
|
func (t *handshakeTransport) id() string {
|
||||||
|
@ -177,15 +161,22 @@ func (t *handshakeTransport) readOnePacket() ([]byte, error) {
|
||||||
|
|
||||||
t.readSinceKex += uint64(len(p))
|
t.readSinceKex += uint64(len(p))
|
||||||
if debugHandshake {
|
if debugHandshake {
|
||||||
msg, err := decode(p)
|
if p[0] == msgChannelData || p[0] == msgChannelExtendedData {
|
||||||
log.Printf("%s got %T %v (%v)", t.id(), msg, msg, err)
|
log.Printf("%s got data (packet %d bytes)", t.id(), len(p))
|
||||||
|
} else {
|
||||||
|
msg, err := decode(p)
|
||||||
|
log.Printf("%s got %T %v (%v)", t.id(), msg, msg, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if p[0] != msgKexInit {
|
if p[0] != msgKexInit {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
err = t.enterKeyExchange(p)
|
|
||||||
|
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
|
|
||||||
|
firstKex := t.sessionID == nil
|
||||||
|
|
||||||
|
err = t.enterKeyExchangeLocked(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// drop connection
|
// drop connection
|
||||||
t.conn.Close()
|
t.conn.Close()
|
||||||
|
@ -193,7 +184,7 @@ func (t *handshakeTransport) readOnePacket() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if debugHandshake {
|
if debugHandshake {
|
||||||
log.Printf("%s exited key exchange, err %v", t.id(), err)
|
log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unblock writers.
|
// Unblock writers.
|
||||||
|
@ -208,28 +199,69 @@ func (t *handshakeTransport) readOnePacket() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.readSinceKex = 0
|
t.readSinceKex = 0
|
||||||
return []byte{msgNewKeys}, nil
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keyChangeCategory describes whether a key exchange is the first on a
|
||||||
|
// connection, or a subsequent one.
|
||||||
|
type keyChangeCategory bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
firstKeyExchange keyChangeCategory = true
|
||||||
|
subsequentKeyExchange keyChangeCategory = false
|
||||||
|
)
|
||||||
|
|
||||||
// sendKexInit sends a key change message, and returns the message
|
// sendKexInit sends a key change message, and returns the message
|
||||||
// that was sent. After initiating the key change, all writes will be
|
// that was sent. After initiating the key change, all writes will be
|
||||||
// blocked until the change is done, and a failed key change will
|
// blocked until the change is done, and a failed key change will
|
||||||
// close the underlying transport. This function is safe for
|
// close the underlying transport. This function is safe for
|
||||||
// concurrent use by multiple goroutines.
|
// concurrent use by multiple goroutines.
|
||||||
func (t *handshakeTransport) sendKexInit() (*kexInitMsg, []byte, error) {
|
func (t *handshakeTransport) sendKexInit(isFirst keyChangeCategory) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
// If this is the initial key change, but we already have a sessionID,
|
||||||
return t.sendKexInitLocked()
|
// then do nothing because the key exchange has already completed
|
||||||
|
// asynchronously.
|
||||||
|
if !isFirst || t.sessionID == nil {
|
||||||
|
_, _, err = t.sendKexInitLocked(isFirst)
|
||||||
|
}
|
||||||
|
t.mu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if isFirst {
|
||||||
|
if packet, err := t.readPacket(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if packet[0] != msgNewKeys {
|
||||||
|
return unexpectedMessageError(msgNewKeys, packet[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *handshakeTransport) requestInitialKeyChange() error {
|
||||||
|
return t.sendKexInit(firstKeyExchange)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *handshakeTransport) requestKeyChange() error {
|
func (t *handshakeTransport) requestKeyChange() error {
|
||||||
_, _, err := t.sendKexInit()
|
return t.sendKexInit(subsequentKeyExchange)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendKexInitLocked sends a key change message. t.mu must be locked
|
// sendKexInitLocked sends a key change message. t.mu must be locked
|
||||||
// while this happens.
|
// while this happens.
|
||||||
func (t *handshakeTransport) sendKexInitLocked() (*kexInitMsg, []byte, error) {
|
func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*kexInitMsg, []byte, error) {
|
||||||
// kexInits may be sent either in response to the other side,
|
// kexInits may be sent either in response to the other side,
|
||||||
// or because our side wants to initiate a key change, so we
|
// 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
|
// may have already sent a kexInit. In that case, don't send a
|
||||||
|
@ -237,6 +269,7 @@ func (t *handshakeTransport) sendKexInitLocked() (*kexInitMsg, []byte, error) {
|
||||||
if t.sentInitMsg != nil {
|
if t.sentInitMsg != nil {
|
||||||
return t.sentInitMsg, t.sentInitPacket, nil
|
return t.sentInitMsg, t.sentInitPacket, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := &kexInitMsg{
|
msg := &kexInitMsg{
|
||||||
KexAlgos: t.config.KeyExchanges,
|
KexAlgos: t.config.KeyExchanges,
|
||||||
CiphersClientServer: t.config.Ciphers,
|
CiphersClientServer: t.config.Ciphers,
|
||||||
|
@ -276,7 +309,7 @@ func (t *handshakeTransport) writePacket(p []byte) error {
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
if t.writtenSinceKex > t.config.RekeyThreshold {
|
if t.writtenSinceKex > t.config.RekeyThreshold {
|
||||||
t.sendKexInitLocked()
|
t.sendKexInitLocked(subsequentKeyExchange)
|
||||||
}
|
}
|
||||||
for t.sentInitMsg != nil && t.writeError == nil {
|
for t.sentInitMsg != nil && t.writeError == nil {
|
||||||
t.cond.Wait()
|
t.cond.Wait()
|
||||||
|
@ -300,12 +333,12 @@ func (t *handshakeTransport) Close() error {
|
||||||
return t.conn.Close()
|
return t.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// enterKeyExchange runs the key exchange.
|
// enterKeyExchange runs the key exchange. t.mu must be held while running this.
|
||||||
func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) error {
|
||||||
if debugHandshake {
|
if debugHandshake {
|
||||||
log.Printf("%s entered key exchange", t.id())
|
log.Printf("%s entered key exchange", t.id())
|
||||||
}
|
}
|
||||||
myInit, myInitPacket, err := t.sendKexInit()
|
myInit, myInitPacket, err := t.sendKexInitLocked(subsequentKeyExchange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -338,7 +371,16 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't send FirstKexFollows, but we handle receiving it.
|
// We don't send FirstKexFollows, but we handle receiving it.
|
||||||
if otherInit.FirstKexFollows && algs.kex != otherInit.KexAlgos[0] {
|
//
|
||||||
|
// 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,
|
// other side sent a kex message for the wrong algorithm,
|
||||||
// which we have to ignore.
|
// which we have to ignore.
|
||||||
if _, err := t.conn.readPacket(); err != nil {
|
if _, err := t.conn.readPacket(); err != nil {
|
||||||
|
@ -362,6 +404,11 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.sessionID == nil {
|
||||||
|
t.sessionID = result.H
|
||||||
|
}
|
||||||
|
result.SessionID = t.sessionID
|
||||||
|
|
||||||
t.conn.prepareKeyChange(algs, result)
|
t.conn.prepareKeyChange(algs, result)
|
||||||
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
|
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -371,6 +418,7 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
||||||
} else if packet[0] != msgNewKeys {
|
} else if packet[0] != msgNewKeys {
|
||||||
return unexpectedMessageError(msgNewKeys, packet[0])
|
return unexpectedMessageError(msgNewKeys, packet[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ type kexResult struct {
|
||||||
Hash crypto.Hash
|
Hash crypto.Hash
|
||||||
|
|
||||||
// The session ID, which is the first H computed. This is used
|
// The session ID, which is the first H computed. This is used
|
||||||
// to signal data inside transport.
|
// to derive key material inside the transport.
|
||||||
SessionID []byte
|
SessionID []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These constants represent the algorithm names for key types supported by this
|
// These constants represent the algorithm names for key types supported by this
|
||||||
|
@ -30,6 +32,7 @@ const (
|
||||||
KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
|
KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
|
||||||
KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
|
KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
|
||||||
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
|
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
|
||||||
|
KeyAlgoED25519 = "ssh-ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parsePubKey parses a public key of the given algorithm.
|
// parsePubKey parses a public key of the given algorithm.
|
||||||
|
@ -42,14 +45,16 @@ func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err err
|
||||||
return parseDSA(in)
|
return parseDSA(in)
|
||||||
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
||||||
return parseECDSA(in)
|
return parseECDSA(in)
|
||||||
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
case KeyAlgoED25519:
|
||||||
|
return parseED25519(in)
|
||||||
|
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01:
|
||||||
cert, err := parseCert(in, certToPrivAlgo(algo))
|
cert, err := parseCert(in, certToPrivAlgo(algo))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return cert, nil, nil
|
return cert, nil, nil
|
||||||
}
|
}
|
||||||
return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", err)
|
return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
|
// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
|
||||||
|
@ -120,7 +125,7 @@ func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip out the begining of the known_host key.
|
// Strip out the beginning of the known_host key.
|
||||||
// This is either an optional marker or a (set of) hostname(s).
|
// This is either an optional marker or a (set of) hostname(s).
|
||||||
keyFields := bytes.Fields(in)
|
keyFields := bytes.Fields(in)
|
||||||
if len(keyFields) < 3 || len(keyFields) > 5 {
|
if len(keyFields) < 3 || len(keyFields) > 5 {
|
||||||
|
@ -276,6 +281,12 @@ type PublicKey interface {
|
||||||
Verify(data []byte, sig *Signature) error
|
Verify(data []byte, sig *Signature) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CryptoPublicKey, if implemented by a PublicKey,
|
||||||
|
// returns the underlying crypto.PublicKey form of the key.
|
||||||
|
type CryptoPublicKey interface {
|
||||||
|
CryptoPublicKey() crypto.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
// A Signer can create signatures that verify against a public key.
|
// A Signer can create signatures that verify against a public key.
|
||||||
type Signer interface {
|
type Signer interface {
|
||||||
// PublicKey returns an associated PublicKey instance.
|
// PublicKey returns an associated PublicKey instance.
|
||||||
|
@ -319,6 +330,8 @@ func parseRSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||||
|
|
||||||
func (r *rsaPublicKey) Marshal() []byte {
|
func (r *rsaPublicKey) Marshal() []byte {
|
||||||
e := new(big.Int).SetInt64(int64(r.E))
|
e := new(big.Int).SetInt64(int64(r.E))
|
||||||
|
// RSA publickey struct layout should match the struct used by
|
||||||
|
// parseRSACert in the x/crypto/ssh/agent package.
|
||||||
wirekey := struct {
|
wirekey := struct {
|
||||||
Name string
|
Name string
|
||||||
E *big.Int
|
E *big.Int
|
||||||
|
@ -341,6 +354,10 @@ func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||||
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob)
|
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||||
|
return (*rsa.PublicKey)(r)
|
||||||
|
}
|
||||||
|
|
||||||
type dsaPublicKey dsa.PublicKey
|
type dsaPublicKey dsa.PublicKey
|
||||||
|
|
||||||
func (r *dsaPublicKey) Type() string {
|
func (r *dsaPublicKey) Type() string {
|
||||||
|
@ -369,6 +386,8 @@ func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *dsaPublicKey) Marshal() []byte {
|
func (k *dsaPublicKey) Marshal() []byte {
|
||||||
|
// DSA publickey struct layout should match the struct used by
|
||||||
|
// parseDSACert in the x/crypto/ssh/agent package.
|
||||||
w := struct {
|
w := struct {
|
||||||
Name string
|
Name string
|
||||||
P, Q, G, Y *big.Int
|
P, Q, G, Y *big.Int
|
||||||
|
@ -407,6 +426,10 @@ func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||||
return errors.New("ssh: signature did not verify")
|
return errors.New("ssh: signature did not verify")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *dsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||||
|
return (*dsa.PublicKey)(k)
|
||||||
|
}
|
||||||
|
|
||||||
type dsaPrivateKey struct {
|
type dsaPrivateKey struct {
|
||||||
*dsa.PrivateKey
|
*dsa.PrivateKey
|
||||||
}
|
}
|
||||||
|
@ -455,6 +478,55 @@ func (key *ecdsaPublicKey) nistID() string {
|
||||||
panic("ssh: unsupported ecdsa key size")
|
panic("ssh: unsupported ecdsa key size")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ed25519PublicKey ed25519.PublicKey
|
||||||
|
|
||||||
|
func (key ed25519PublicKey) Type() string {
|
||||||
|
return KeyAlgoED25519
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseED25519(in []byte) (out PublicKey, rest []byte, err error) {
|
||||||
|
var w struct {
|
||||||
|
KeyBytes []byte
|
||||||
|
Rest []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Unmarshal(in, &w); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ed25519.PublicKey(w.KeyBytes)
|
||||||
|
|
||||||
|
return (ed25519PublicKey)(key), w.Rest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key ed25519PublicKey) Marshal() []byte {
|
||||||
|
w := struct {
|
||||||
|
Name string
|
||||||
|
KeyBytes []byte
|
||||||
|
}{
|
||||||
|
KeyAlgoED25519,
|
||||||
|
[]byte(key),
|
||||||
|
}
|
||||||
|
return Marshal(&w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key ed25519PublicKey) Verify(b []byte, sig *Signature) error {
|
||||||
|
if sig.Format != key.Type() {
|
||||||
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
edKey := (ed25519.PublicKey)(key)
|
||||||
|
if ok := ed25519.Verify(edKey, b, sig.Blob); !ok {
|
||||||
|
return errors.New("ssh: signature did not verify")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k ed25519PublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||||
|
return ed25519.PublicKey(k)
|
||||||
|
}
|
||||||
|
|
||||||
func supportedEllipticCurve(curve elliptic.Curve) bool {
|
func supportedEllipticCurve(curve elliptic.Curve) bool {
|
||||||
return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
|
return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
|
||||||
}
|
}
|
||||||
|
@ -507,6 +579,8 @@ func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
||||||
func (key *ecdsaPublicKey) Marshal() []byte {
|
func (key *ecdsaPublicKey) Marshal() []byte {
|
||||||
// See RFC 5656, section 3.1.
|
// See RFC 5656, section 3.1.
|
||||||
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
|
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
|
||||||
|
// ECDSA publickey struct layout should match the struct used by
|
||||||
|
// parseECDSACert in the x/crypto/ssh/agent package.
|
||||||
w := struct {
|
w := struct {
|
||||||
Name string
|
Name string
|
||||||
ID string
|
ID string
|
||||||
|
@ -548,6 +622,10 @@ func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
||||||
return errors.New("ssh: signature did not verify")
|
return errors.New("ssh: signature did not verify")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||||
|
return (*ecdsa.PublicKey)(k)
|
||||||
|
}
|
||||||
|
|
||||||
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
||||||
// *ecdsa.PrivateKey or any other crypto.Signer and returns a corresponding
|
// *ecdsa.PrivateKey or any other crypto.Signer and returns a corresponding
|
||||||
// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
|
// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
|
||||||
|
@ -591,13 +669,19 @@ func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||||
hashFunc = crypto.SHA1
|
hashFunc = crypto.SHA1
|
||||||
case *ecdsaPublicKey:
|
case *ecdsaPublicKey:
|
||||||
hashFunc = ecHash(key.Curve)
|
hashFunc = ecHash(key.Curve)
|
||||||
|
case ed25519PublicKey:
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := hashFunc.New()
|
var digest []byte
|
||||||
h.Write(data)
|
if hashFunc != 0 {
|
||||||
digest := h.Sum(nil)
|
h := hashFunc.New()
|
||||||
|
h.Write(data)
|
||||||
|
digest = h.Sum(nil)
|
||||||
|
} else {
|
||||||
|
digest = data
|
||||||
|
}
|
||||||
|
|
||||||
signature, err := s.signer.Sign(rand, digest, hashFunc)
|
signature, err := s.signer.Sign(rand, digest, hashFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -637,9 +721,9 @@ func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or
|
// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey,
|
||||||
// any other crypto.Signer and returns a corresponding Signer instance. ECDSA
|
// ed25519.PublicKey, or any other crypto.Signer and returns a corresponding
|
||||||
// keys must use P-256, P-384 or P-521.
|
// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
|
||||||
func NewPublicKey(key interface{}) (PublicKey, error) {
|
func NewPublicKey(key interface{}) (PublicKey, error) {
|
||||||
switch key := key.(type) {
|
switch key := key.(type) {
|
||||||
case *rsa.PublicKey:
|
case *rsa.PublicKey:
|
||||||
|
@ -651,6 +735,8 @@ func NewPublicKey(key interface{}) (PublicKey, error) {
|
||||||
return (*ecdsaPublicKey)(key), nil
|
return (*ecdsaPublicKey)(key), nil
|
||||||
case *dsa.PublicKey:
|
case *dsa.PublicKey:
|
||||||
return (*dsaPublicKey)(key), nil
|
return (*dsaPublicKey)(key), nil
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
return (ed25519PublicKey)(key), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
||||||
}
|
}
|
||||||
|
@ -667,6 +753,14 @@ func ParsePrivateKey(pemBytes []byte) (Signer, error) {
|
||||||
return NewSignerFromKey(key)
|
return NewSignerFromKey(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encryptedBlock tells whether a private key is
|
||||||
|
// encrypted by examining its Proc-Type header
|
||||||
|
// for a mention of ENCRYPTED
|
||||||
|
// according to RFC 1421 Section 4.6.1.1.
|
||||||
|
func encryptedBlock(block *pem.Block) bool {
|
||||||
|
return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED")
|
||||||
|
}
|
||||||
|
|
||||||
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It
|
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It
|
||||||
// supports RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys.
|
// supports RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys.
|
||||||
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
||||||
|
@ -675,6 +769,10 @@ func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
||||||
return nil, errors.New("ssh: no key found")
|
return nil, errors.New("ssh: no key found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if encryptedBlock(block) {
|
||||||
|
return nil, errors.New("ssh: cannot decode encrypted private keys")
|
||||||
|
}
|
||||||
|
|
||||||
switch block.Type {
|
switch block.Type {
|
||||||
case "RSA PRIVATE KEY":
|
case "RSA PRIVATE KEY":
|
||||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
@ -682,6 +780,8 @@ func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
||||||
return x509.ParseECPrivateKey(block.Bytes)
|
return x509.ParseECPrivateKey(block.Bytes)
|
||||||
case "DSA PRIVATE KEY":
|
case "DSA PRIVATE KEY":
|
||||||
return ParseDSAPrivateKey(block.Bytes)
|
return ParseDSAPrivateKey(block.Bytes)
|
||||||
|
case "OPENSSH PRIVATE KEY":
|
||||||
|
return parseOpenSSHPrivateKey(block.Bytes)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
||||||
}
|
}
|
||||||
|
@ -718,3 +818,63 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
||||||
X: k.Pub,
|
X: k.Pub,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implemented based on the documentation at
|
||||||
|
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
|
||||||
|
func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
|
||||||
|
magic := append([]byte("openssh-key-v1"), 0)
|
||||||
|
if !bytes.Equal(magic, key[0:len(magic)]) {
|
||||||
|
return nil, errors.New("ssh: invalid openssh private key format")
|
||||||
|
}
|
||||||
|
remaining := key[len(magic):]
|
||||||
|
|
||||||
|
var w struct {
|
||||||
|
CipherName string
|
||||||
|
KdfName string
|
||||||
|
KdfOpts string
|
||||||
|
NumKeys uint32
|
||||||
|
PubKey []byte
|
||||||
|
PrivKeyBlock []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Unmarshal(remaining, &w); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pk1 := struct {
|
||||||
|
Check1 uint32
|
||||||
|
Check2 uint32
|
||||||
|
Keytype string
|
||||||
|
Pub []byte
|
||||||
|
Priv []byte
|
||||||
|
Comment string
|
||||||
|
Pad []byte `ssh:"rest"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pk1.Check1 != pk1.Check2 {
|
||||||
|
return nil, errors.New("ssh: checkint mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only handle ed25519 keys currently
|
||||||
|
if pk1.Keytype != KeyAlgoED25519 {
|
||||||
|
return nil, errors.New("ssh: unhandled key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, b := range pk1.Pad {
|
||||||
|
if int(b) != i+1 {
|
||||||
|
return nil, errors.New("ssh: padding not as expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pk1.Priv) != ed25519.PrivateKeySize {
|
||||||
|
return nil, errors.New("ssh: private key unexpected length")
|
||||||
|
}
|
||||||
|
|
||||||
|
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
|
||||||
|
copy(pk, pk1.Priv)
|
||||||
|
return &pk, nil
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These are SSH message type numbers. They are scattered around several
|
// These are SSH message type numbers. They are scattered around several
|
||||||
|
@ -47,7 +48,7 @@ type disconnectMsg struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *disconnectMsg) Error() string {
|
func (d *disconnectMsg) Error() string {
|
||||||
return fmt.Sprintf("ssh: disconnect reason %d: %s", d.Reason, d.Message)
|
return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// See RFC 4253, section 7.1.
|
// See RFC 4253, section 7.1.
|
||||||
|
@ -124,6 +125,10 @@ type userAuthRequestMsg struct {
|
||||||
Payload []byte `ssh:"rest"`
|
Payload []byte `ssh:"rest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used for debug printouts of packets.
|
||||||
|
type userAuthSuccessMsg struct {
|
||||||
|
}
|
||||||
|
|
||||||
// See RFC 4252, section 5.1
|
// See RFC 4252, section 5.1
|
||||||
const msgUserAuthFailure = 51
|
const msgUserAuthFailure = 51
|
||||||
|
|
||||||
|
@ -158,6 +163,13 @@ type channelOpenMsg struct {
|
||||||
const msgChannelExtendedData = 95
|
const msgChannelExtendedData = 95
|
||||||
const msgChannelData = 94
|
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.
|
// See RFC 4254, section 5.1.
|
||||||
const msgChannelOpenConfirm = 91
|
const msgChannelOpenConfirm = 91
|
||||||
|
|
||||||
|
@ -255,17 +267,19 @@ type userAuthPubKeyOkMsg struct {
|
||||||
PubKey []byte
|
PubKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// typeTag returns the type byte for the given type. The type should
|
// typeTags returns the possible type bytes for the given reflect.Type, which
|
||||||
// be struct.
|
// should be a struct. The possible values are separated by a '|' character.
|
||||||
func typeTag(structType reflect.Type) byte {
|
func typeTags(structType reflect.Type) (tags []byte) {
|
||||||
var tag byte
|
tagStr := structType.Field(0).Tag.Get("sshtype")
|
||||||
var tagStr string
|
|
||||||
tagStr = structType.Field(0).Tag.Get("sshtype")
|
for _, tag := range strings.Split(tagStr, "|") {
|
||||||
i, err := strconv.Atoi(tagStr)
|
i, err := strconv.Atoi(tag)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tag = byte(i)
|
tags = append(tags, byte(i))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tag
|
|
||||||
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
func fieldError(t reflect.Type, field int, problem string) error {
|
func fieldError(t reflect.Type, field int, problem string) error {
|
||||||
|
@ -279,19 +293,34 @@ var errShortRead = errors.New("ssh: short read")
|
||||||
|
|
||||||
// Unmarshal parses data in SSH wire format into a structure. The out
|
// 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
|
// argument should be a pointer to struct. If the first member of the
|
||||||
// struct has the "sshtype" tag set to a number in decimal, the packet
|
// struct has the "sshtype" tag set to a '|'-separated set of numbers
|
||||||
// must start that number. In case of error, Unmarshal returns a
|
// in decimal, the packet must start with one of those numbers. In
|
||||||
// ParseError or UnexpectedMessageError.
|
// case of error, Unmarshal returns a ParseError or
|
||||||
|
// UnexpectedMessageError.
|
||||||
func Unmarshal(data []byte, out interface{}) error {
|
func Unmarshal(data []byte, out interface{}) error {
|
||||||
v := reflect.ValueOf(out).Elem()
|
v := reflect.ValueOf(out).Elem()
|
||||||
structType := v.Type()
|
structType := v.Type()
|
||||||
expectedType := typeTag(structType)
|
expectedTypes := typeTags(structType)
|
||||||
|
|
||||||
|
var expectedType byte
|
||||||
|
if len(expectedTypes) > 0 {
|
||||||
|
expectedType = expectedTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return parseError(expectedType)
|
return parseError(expectedType)
|
||||||
}
|
}
|
||||||
if expectedType > 0 {
|
|
||||||
if data[0] != expectedType {
|
if len(expectedTypes) > 0 {
|
||||||
return unexpectedMessageError(expectedType, data[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:]
|
data = data[1:]
|
||||||
}
|
}
|
||||||
|
@ -375,7 +404,7 @@ func Unmarshal(data []byte, out interface{}) error {
|
||||||
return fieldError(structType, i, "pointer to unsupported type")
|
return fieldError(structType, i, "pointer to unsupported type")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fieldError(structType, i, "unsupported type")
|
return fieldError(structType, i, fmt.Sprintf("unsupported type: %v", t))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,9 +427,9 @@ func Marshal(msg interface{}) []byte {
|
||||||
|
|
||||||
func marshalStruct(out []byte, msg interface{}) []byte {
|
func marshalStruct(out []byte, msg interface{}) []byte {
|
||||||
v := reflect.Indirect(reflect.ValueOf(msg))
|
v := reflect.Indirect(reflect.ValueOf(msg))
|
||||||
msgType := typeTag(v.Type())
|
msgTypes := typeTags(v.Type())
|
||||||
if msgType > 0 {
|
if len(msgTypes) > 0 {
|
||||||
out = append(out, msgType)
|
out = append(out, msgTypes[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, n := 0, v.NumField(); i < n; i++ {
|
for i, n := 0, v.NumField(); i < n; i++ {
|
||||||
|
@ -687,6 +716,8 @@ func decode(packet []byte) (interface{}, error) {
|
||||||
msg = new(kexDHReplyMsg)
|
msg = new(kexDHReplyMsg)
|
||||||
case msgUserAuthRequest:
|
case msgUserAuthRequest:
|
||||||
msg = new(userAuthRequestMsg)
|
msg = new(userAuthRequestMsg)
|
||||||
|
case msgUserAuthSuccess:
|
||||||
|
return new(userAuthSuccessMsg), nil
|
||||||
case msgUserAuthFailure:
|
case msgUserAuthFailure:
|
||||||
msg = new(userAuthFailureMsg)
|
msg = new(userAuthFailureMsg)
|
||||||
case msgUserAuthPubKeyOk:
|
case msgUserAuthPubKeyOk:
|
||||||
|
@ -699,6 +730,8 @@ func decode(packet []byte) (interface{}, error) {
|
||||||
msg = new(globalRequestFailureMsg)
|
msg = new(globalRequestFailureMsg)
|
||||||
case msgChannelOpen:
|
case msgChannelOpen:
|
||||||
msg = new(channelOpenMsg)
|
msg = new(channelOpenMsg)
|
||||||
|
case msgChannelData:
|
||||||
|
msg = new(channelDataMsg)
|
||||||
case msgChannelOpenConfirm:
|
case msgChannelOpenConfirm:
|
||||||
msg = new(channelOpenConfirmMsg)
|
msg = new(channelOpenConfirmMsg)
|
||||||
case msgChannelOpenFailure:
|
case msgChannelOpenFailure:
|
||||||
|
|
|
@ -131,6 +131,9 @@ func newMux(p packetConn) *mux {
|
||||||
|
|
||||||
func (m *mux) sendMessage(msg interface{}) error {
|
func (m *mux) sendMessage(msg interface{}) error {
|
||||||
p := Marshal(msg)
|
p := Marshal(msg)
|
||||||
|
if debugMux {
|
||||||
|
log.Printf("send global(%d): %#v", m.chanList.offset, msg)
|
||||||
|
}
|
||||||
return m.conn.writePacket(p)
|
return m.conn.writePacket(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,18 +178,6 @@ func (m *mux) ackRequest(ok bool, data []byte) error {
|
||||||
return m.sendMessage(globalRequestFailureMsg{Data: data})
|
return m.sendMessage(globalRequestFailureMsg{Data: data})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(hanwen): Disconnect is a transport layer message. We should
|
|
||||||
// probably send and receive Disconnect somewhere in the transport
|
|
||||||
// code.
|
|
||||||
|
|
||||||
// Disconnect sends a disconnect message.
|
|
||||||
func (m *mux) Disconnect(reason uint32, message string) error {
|
|
||||||
return m.sendMessage(disconnectMsg{
|
|
||||||
Reason: reason,
|
|
||||||
Message: message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mux) Close() error {
|
func (m *mux) Close() error {
|
||||||
return m.conn.Close()
|
return m.conn.Close()
|
||||||
}
|
}
|
||||||
|
@ -236,11 +227,6 @@ func (m *mux) onePacket() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch packet[0] {
|
switch packet[0] {
|
||||||
case msgNewKeys:
|
|
||||||
// Ignore notification of key change.
|
|
||||||
return nil
|
|
||||||
case msgDisconnect:
|
|
||||||
return m.handleDisconnect(packet)
|
|
||||||
case msgChannelOpen:
|
case msgChannelOpen:
|
||||||
return m.handleChannelOpen(packet)
|
return m.handleChannelOpen(packet)
|
||||||
case msgGlobalRequest, msgRequestSuccess, msgRequestFailure:
|
case msgGlobalRequest, msgRequestSuccess, msgRequestFailure:
|
||||||
|
@ -260,18 +246,6 @@ func (m *mux) onePacket() error {
|
||||||
return ch.handlePacket(packet)
|
return ch.handlePacket(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mux) handleDisconnect(packet []byte) error {
|
|
||||||
var d disconnectMsg
|
|
||||||
if err := Unmarshal(packet, &d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if debugMux {
|
|
||||||
log.Printf("caught disconnect: %v", d)
|
|
||||||
}
|
|
||||||
return &d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mux) handleGlobalPacket(packet []byte) error {
|
func (m *mux) handleGlobalPacket(packet []byte) error {
|
||||||
msg, err := decode(packet)
|
msg, err := decode(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -188,16 +188,10 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
|
||||||
tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */)
|
tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */)
|
||||||
s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config)
|
s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config)
|
||||||
|
|
||||||
if err := s.transport.requestKeyChange(); err != nil {
|
if err := s.transport.requestInitialKeyChange(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if packet, err := s.transport.readPacket(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if packet[0] != msgNewKeys {
|
|
||||||
return nil, unexpectedMessageError(msgNewKeys, packet[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// We just did the key change, so the session ID is established.
|
// We just did the key change, so the session ID is established.
|
||||||
s.sessionID = s.transport.getSessionID()
|
s.sessionID = s.transport.getSessionID()
|
||||||
|
|
||||||
|
@ -230,7 +224,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
|
||||||
|
|
||||||
func isAcceptableAlgo(algo string) bool {
|
func isAcceptableAlgo(algo string) bool {
|
||||||
switch algo {
|
switch algo {
|
||||||
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519,
|
||||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -290,7 +284,6 @@ userAuthLoop:
|
||||||
switch userAuthReq.Method {
|
switch userAuthReq.Method {
|
||||||
case "none":
|
case "none":
|
||||||
if config.NoClientAuth {
|
if config.NoClientAuth {
|
||||||
s.user = ""
|
|
||||||
authErr = nil
|
authErr = nil
|
||||||
}
|
}
|
||||||
case "password":
|
case "password":
|
||||||
|
|
|
@ -9,6 +9,7 @@ package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -281,9 +282,10 @@ func (s *Session) Start(cmd string) error {
|
||||||
// copying stdin, stdout, and stderr, and exits with a zero exit
|
// copying stdin, stdout, and stderr, and exits with a zero exit
|
||||||
// status.
|
// status.
|
||||||
//
|
//
|
||||||
// If the command fails to run or doesn't complete successfully, the
|
// If the remote server does not send an exit status, an error of type
|
||||||
// error is of type *ExitError. Other error types may be
|
// *ExitMissingError is returned. If the command completes
|
||||||
// returned for I/O problems.
|
// 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 {
|
func (s *Session) Run(cmd string) error {
|
||||||
err := s.Start(cmd)
|
err := s.Start(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -370,9 +372,10 @@ func (s *Session) start() error {
|
||||||
// copying stdin, stdout, and stderr, and exits with a zero exit
|
// copying stdin, stdout, and stderr, and exits with a zero exit
|
||||||
// status.
|
// status.
|
||||||
//
|
//
|
||||||
// If the command fails to run or doesn't complete successfully, the
|
// If the remote server does not send an exit status, an error of type
|
||||||
// error is of type *ExitError. Other error types may be
|
// *ExitMissingError is returned. If the command completes
|
||||||
// returned for I/O problems.
|
// 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 {
|
func (s *Session) Wait() error {
|
||||||
if !s.started {
|
if !s.started {
|
||||||
return errors.New("ssh: session not started")
|
return errors.New("ssh: session not started")
|
||||||
|
@ -400,8 +403,7 @@ func (s *Session) wait(reqs <-chan *Request) error {
|
||||||
for msg := range reqs {
|
for msg := range reqs {
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case "exit-status":
|
case "exit-status":
|
||||||
d := msg.Payload
|
wm.status = int(binary.BigEndian.Uint32(msg.Payload))
|
||||||
wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
|
|
||||||
case "exit-signal":
|
case "exit-signal":
|
||||||
var sigval struct {
|
var sigval struct {
|
||||||
Signal string
|
Signal string
|
||||||
|
@ -431,16 +433,29 @@ func (s *Session) wait(reqs <-chan *Request) error {
|
||||||
if wm.status == -1 {
|
if wm.status == -1 {
|
||||||
// exit-status was never sent from server
|
// exit-status was never sent from server
|
||||||
if wm.signal == "" {
|
if wm.signal == "" {
|
||||||
return errors.New("wait: remote command exited without exit status or exit 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
|
wm.status = 128
|
||||||
if _, ok := signals[Signal(wm.signal)]; ok {
|
if _, ok := signals[Signal(wm.signal)]; ok {
|
||||||
wm.status += signals[Signal(wm.signal)]
|
wm.status += signals[Signal(wm.signal)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ExitError{wm}
|
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() {
|
func (s *Session) stdin() {
|
||||||
if s.stdinpipe {
|
if s.stdinpipe {
|
||||||
return
|
return
|
||||||
|
@ -601,5 +616,12 @@ func (w Waitmsg) Lang() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w Waitmsg) String() string {
|
func (w Waitmsg) String() string {
|
||||||
return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
gcmCipherID = "aes128-gcm@openssh.com"
|
gcmCipherID = "aes128-gcm@openssh.com"
|
||||||
aes128cbcID = "aes128-cbc"
|
aes128cbcID = "aes128-cbc"
|
||||||
|
tripledescbcID = "3des-cbc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// packetConn represents a transport that implements packet based
|
// packetConn represents a transport that implements packet based
|
||||||
|
@ -39,19 +40,6 @@ type transport struct {
|
||||||
rand io.Reader
|
rand io.Reader
|
||||||
|
|
||||||
io.Closer
|
io.Closer
|
||||||
|
|
||||||
// Initial H used for the session ID. Once assigned this does
|
|
||||||
// not change, even during subsequent key exchanges.
|
|
||||||
sessionID []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSessionID returns the ID of the SSH connection. The return value
|
|
||||||
// should not be modified.
|
|
||||||
func (t *transport) getSessionID() []byte {
|
|
||||||
if t.sessionID == nil {
|
|
||||||
panic("session ID not set yet")
|
|
||||||
}
|
|
||||||
return t.sessionID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// packetCipher represents a combination of SSH encryption/MAC
|
// packetCipher represents a combination of SSH encryption/MAC
|
||||||
|
@ -81,12 +69,6 @@ type connectionState struct {
|
||||||
// both directions are triggered by reading and writing a msgNewKey packet
|
// both directions are triggered by reading and writing a msgNewKey packet
|
||||||
// respectively.
|
// respectively.
|
||||||
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
|
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
|
||||||
if t.sessionID == nil {
|
|
||||||
t.sessionID = kexResult.H
|
|
||||||
}
|
|
||||||
|
|
||||||
kexResult.SessionID = t.sessionID
|
|
||||||
|
|
||||||
if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil {
|
if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
|
@ -114,12 +96,27 @@ func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
|
||||||
err = errors.New("ssh: zero length packet")
|
err = errors.New("ssh: zero length packet")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(packet) > 0 && packet[0] == msgNewKeys {
|
if len(packet) > 0 {
|
||||||
select {
|
switch packet[0] {
|
||||||
case cipher := <-s.pendingKeyChange:
|
case msgNewKeys:
|
||||||
s.packetCipher = cipher
|
select {
|
||||||
default:
|
case cipher := <-s.pendingKeyChange:
|
||||||
return nil, errors.New("ssh: got bogus newkeys message.")
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,6 +220,10 @@ func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (pac
|
||||||
return newAESCBCCipher(iv, key, macKey, algs)
|
return newAESCBCCipher(iv, key, macKey, algs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if algs.Cipher == tripledescbcID {
|
||||||
|
return newTripleDESCBCCipher(iv, key, macKey, algs)
|
||||||
|
}
|
||||||
|
|
||||||
c := &streamPacketCipher{
|
c := &streamPacketCipher{
|
||||||
mac: macModes[algs.MAC].new(macKey),
|
mac: macModes[algs.MAC].new(macKey),
|
||||||
}
|
}
|
||||||
|
|
|
@ -705,20 +705,34 @@
|
||||||
"path": "golang.org/x/crypto/curve25519",
|
"path": "golang.org/x/crypto/curve25519",
|
||||||
"revision": "1f22c0103821b9390939b6776727195525381532"
|
"revision": "1f22c0103821b9390939b6776727195525381532"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=",
|
||||||
|
"path": "golang.org/x/crypto/ed25519",
|
||||||
|
"revision": "7682e7e3945130cf3cde089834664f68afdd1523",
|
||||||
|
"revisionTime": "2016-10-03T20:54:26Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=",
|
||||||
|
"path": "golang.org/x/crypto/ed25519/internal/edwards25519",
|
||||||
|
"revision": "7682e7e3945130cf3cde089834664f68afdd1523",
|
||||||
|
"revisionTime": "2016-10-03T20:54:26Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "MCeXr2RNeiG1XG6V+er1OR0qyeo=",
|
"checksumSHA1": "MCeXr2RNeiG1XG6V+er1OR0qyeo=",
|
||||||
"path": "golang.org/x/crypto/md4",
|
"path": "golang.org/x/crypto/md4",
|
||||||
"revision": "1f22c0103821b9390939b6776727195525381532"
|
"revision": "1f22c0103821b9390939b6776727195525381532"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "locu61AZ1k1skk45VLE2zpDaVTA=",
|
"checksumSHA1": "1LydpuiE3oBdkbYvSdKKwe9lsLs=",
|
||||||
"path": "golang.org/x/crypto/ssh",
|
"path": "golang.org/x/crypto/ssh",
|
||||||
"revision": "1f22c0103821b9390939b6776727195525381532"
|
"revision": "7682e7e3945130cf3cde089834664f68afdd1523",
|
||||||
|
"revisionTime": "2016-10-03T20:54:26Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "XQix3lM21r/Y7my2/4JmTsNeYpQ=",
|
"checksumSHA1": "SJ3Ma3Ozavxpbh1usZWBCnzMKIc=",
|
||||||
"path": "golang.org/x/crypto/ssh/agent",
|
"path": "golang.org/x/crypto/ssh/agent",
|
||||||
"revision": "1f22c0103821b9390939b6776727195525381532"
|
"revision": "7682e7e3945130cf3cde089834664f68afdd1523",
|
||||||
|
"revisionTime": "2016-10-03T20:54:26Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "5ARrN3Zq+E9zazFb/N+b08Serys=",
|
"checksumSHA1": "5ARrN3Zq+E9zazFb/N+b08Serys=",
|
||||||
|
|
Loading…
Reference in New Issue