Merge pull request #17354 from hashicorp/jbardin/known_hosts
Verify host keys in ssh connections
This commit is contained in:
commit
e41b29d096
|
@ -125,11 +125,13 @@ func (c *Communicator) Connect(o terraform.UIOutput) (err error) {
|
||||||
" User: %s\n"+
|
" User: %s\n"+
|
||||||
" Password: %t\n"+
|
" Password: %t\n"+
|
||||||
" Private key: %t\n"+
|
" Private key: %t\n"+
|
||||||
" SSH Agent: %t",
|
" SSH Agent: %t\n"+
|
||||||
|
" Checking Host Key: %t",
|
||||||
c.connInfo.Host, c.connInfo.User,
|
c.connInfo.Host, c.connInfo.User,
|
||||||
c.connInfo.Password != "",
|
c.connInfo.Password != "",
|
||||||
c.connInfo.PrivateKey != "",
|
c.connInfo.PrivateKey != "",
|
||||||
c.connInfo.Agent,
|
c.connInfo.Agent,
|
||||||
|
c.connInfo.HostKey != "",
|
||||||
))
|
))
|
||||||
|
|
||||||
if c.connInfo.BastionHost != "" {
|
if c.connInfo.BastionHost != "" {
|
||||||
|
@ -139,11 +141,13 @@ func (c *Communicator) Connect(o terraform.UIOutput) (err error) {
|
||||||
" User: %s\n"+
|
" User: %s\n"+
|
||||||
" Password: %t\n"+
|
" Password: %t\n"+
|
||||||
" Private key: %t\n"+
|
" Private key: %t\n"+
|
||||||
" SSH Agent: %t",
|
" SSH Agent: %t\n"+
|
||||||
|
" Checking Host Key: %t",
|
||||||
c.connInfo.BastionHost, c.connInfo.BastionUser,
|
c.connInfo.BastionHost, c.connInfo.BastionUser,
|
||||||
c.connInfo.BastionPassword != "",
|
c.connInfo.BastionPassword != "",
|
||||||
c.connInfo.BastionPrivateKey != "",
|
c.connInfo.BastionPrivateKey != "",
|
||||||
c.connInfo.Agent,
|
c.connInfo.Agent,
|
||||||
|
c.connInfo.BastionHostKey != "",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ package ssh
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -50,21 +52,26 @@ gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
|
||||||
NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
|
NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
|
||||||
-----END RSA PRIVATE KEY-----`
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
|
||||||
var serverConfig = &ssh.ServerConfig{
|
// this cert was signed by the key from testCAPublicKey
|
||||||
|
const testServerHostCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgvQ3Bs1ex7277b9q6I0fNaWsVEC16f+LcT8RLPSVMEVMAAAADAQABAAABAQDX2UZWxOohPmKI1hGCehjULCRsRNblyr5HOTm/+ROV/fVelJTvQdVaRtMREQKNph1czaAZxtv6zGmroa1d/UzeRWibJyqHHCE+/gKvpenhZP+OQXH3P4UXOl6h0YlaM4fovYfm5fUK+v0QN1Cn2338nfb+oEWe1jwbChQj/L/UxJOYyIW26l0w4M3Tri93eDIwpPCuVDy1kzppi7I4+y60uVRjsznHkXAwNi+c8NJ7JP8jDTOzcH40LKp54x3ZPtjNAWdEBOPQzuszkuhKzsNWpWuI4QAGywXIuPfU9uhqguE4qByqgz2SGQ3OvsUdW+L4OFgzaMPQPC+pks3o2acvAAAAAAAAAAAAAAACAAAAB2NhLXRlc3QAAAANAAAACTEyNy4wLjAuMQAAAABag0jkAAAAAHDcHtAAAAAAAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81AAABDwAAAAdzc2gtcnNhAAABAEyoiVkZ5z79nh3WSU5mU2U7e2BItnnEqsJIm9EN+35uG0yORSXmQoaa9mtli7G3r79tyqEJd/C95EdNvU/9TjaoDcbH8OHP+Ue9XSfUzBuQ6bGSXe6mlZlO7QJ1cIyWphFP3MkrweDSiJ+SpeXzLzZkiJ7zKv5czhBEyG/MujFgvikotL+eUNG42y2cgsesXSjENSBS3l11q55a+RM2QKt3W32im8CsSxrH6Mz6p4JXQNgsVvZRknLxNlWXULFB2HLTunPKzJNMTf6xZf66oivSBAXVIdNKhlVpAQ3dT/dW5K6J4aQF/hjWByyLprFwZ16cPDqvtalnTCpbRYelNbw=`
|
||||||
|
|
||||||
|
const testCAPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81`
|
||||||
|
|
||||||
|
func newMockLineServer(t *testing.T, signer ssh.Signer) string {
|
||||||
|
serverConfig := &ssh.ServerConfig{
|
||||||
PasswordCallback: acceptUserPass("user", "pass"),
|
PasswordCallback: acceptUserPass("user", "pass"),
|
||||||
PublicKeyCallback: acceptPublicKey(testClientPublicKey),
|
PublicKeyCallback: acceptPublicKey(testClientPublicKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
var err error
|
||||||
// Parse and set the private key of the server, required to accept connections
|
if signer == nil {
|
||||||
signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
|
signer, err = ssh.ParsePrivateKey([]byte(testServerPrivateKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("unable to parse private key: " + err.Error())
|
t.Fatalf("unable to parse private key: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
serverConfig.AddHostKey(signer)
|
serverConfig.AddHostKey(signer)
|
||||||
}
|
|
||||||
|
|
||||||
func newMockLineServer(t *testing.T) string {
|
|
||||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to listen for connection: %s", err)
|
t.Fatalf("Unable to listen for connection: %s", err)
|
||||||
|
@ -111,7 +118,7 @@ func newMockLineServer(t *testing.T) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew_Invalid(t *testing.T) {
|
func TestNew_Invalid(t *testing.T) {
|
||||||
address := newMockLineServer(t)
|
address := newMockLineServer(t, nil)
|
||||||
parts := strings.Split(address, ":")
|
parts := strings.Split(address, ":")
|
||||||
|
|
||||||
r := &terraform.InstanceState{
|
r := &terraform.InstanceState{
|
||||||
|
@ -139,7 +146,7 @@ func TestNew_Invalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStart(t *testing.T) {
|
func TestStart(t *testing.T) {
|
||||||
address := newMockLineServer(t)
|
address := newMockLineServer(t, nil)
|
||||||
parts := strings.Split(address, ":")
|
parts := strings.Split(address, ":")
|
||||||
|
|
||||||
r := &terraform.InstanceState{
|
r := &terraform.InstanceState{
|
||||||
|
@ -171,6 +178,148 @@ func TestStart(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHostKey(t *testing.T) {
|
||||||
|
// get the server's public key
|
||||||
|
signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
|
||||||
|
if err != nil {
|
||||||
|
panic("unable to parse private key: " + err.Error())
|
||||||
|
}
|
||||||
|
pubKey := fmt.Sprintf("ssh-rsa %s", base64.StdEncoding.EncodeToString(signer.PublicKey().Marshal()))
|
||||||
|
|
||||||
|
address := newMockLineServer(t, nil)
|
||||||
|
host, p, _ := net.SplitHostPort(address)
|
||||||
|
port, _ := strconv.Atoi(p)
|
||||||
|
|
||||||
|
connInfo := &connectionInfo{
|
||||||
|
User: "user",
|
||||||
|
Password: "pass",
|
||||||
|
Host: host,
|
||||||
|
HostKey: pubKey,
|
||||||
|
Port: port,
|
||||||
|
Timeout: "30s",
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := prepareSSHConfig(connInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Communicator{
|
||||||
|
connInfo: connInfo,
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd remote.Cmd
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
cmd.Command = "echo foo"
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
|
||||||
|
if err := c.Start(&cmd); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := c.Disconnect(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now check with the wrong HostKey
|
||||||
|
address = newMockLineServer(t, nil)
|
||||||
|
_, p, _ = net.SplitHostPort(address)
|
||||||
|
port, _ = strconv.Atoi(p)
|
||||||
|
|
||||||
|
connInfo.HostKey = testClientPublicKey
|
||||||
|
connInfo.Port = port
|
||||||
|
|
||||||
|
cfg, err = prepareSSHConfig(connInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &Communicator{
|
||||||
|
connInfo: connInfo,
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Start(&cmd)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "mismatch") {
|
||||||
|
t.Fatalf("expected host key mismatch, got error:%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostCert(t *testing.T) {
|
||||||
|
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testServerHostCert))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err = ssh.NewCertSigner(pk.(*ssh.Certificate), signer)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
address := newMockLineServer(t, signer)
|
||||||
|
host, p, _ := net.SplitHostPort(address)
|
||||||
|
port, _ := strconv.Atoi(p)
|
||||||
|
|
||||||
|
connInfo := &connectionInfo{
|
||||||
|
User: "user",
|
||||||
|
Password: "pass",
|
||||||
|
Host: host,
|
||||||
|
HostKey: testCAPublicKey,
|
||||||
|
Port: port,
|
||||||
|
Timeout: "30s",
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := prepareSSHConfig(connInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Communicator{
|
||||||
|
connInfo: connInfo,
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd remote.Cmd
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
cmd.Command = "echo foo"
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
|
||||||
|
if err := c.Start(&cmd); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := c.Disconnect(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now check with the wrong HostKey
|
||||||
|
address = newMockLineServer(t, signer)
|
||||||
|
_, p, _ = net.SplitHostPort(address)
|
||||||
|
port, _ = strconv.Atoi(p)
|
||||||
|
|
||||||
|
connInfo.HostKey = testClientPublicKey
|
||||||
|
connInfo.Port = port
|
||||||
|
|
||||||
|
cfg, err = prepareSSHConfig(connInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &Communicator{
|
||||||
|
connInfo: connInfo,
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.Start(&cmd)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "authorities") {
|
||||||
|
t.Fatalf("expected host key mismatch, got error:%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccUploadFile(t *testing.T) {
|
func TestAccUploadFile(t *testing.T) {
|
||||||
// use the local ssh server and scp binary to check uploads
|
// use the local ssh server and scp binary to check uploads
|
||||||
if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
|
if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/xanzy/ssh-agent"
|
"github.com/xanzy/ssh-agent"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/crypto/ssh/agent"
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
"golang.org/x/crypto/ssh/knownhosts"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -43,6 +44,7 @@ type connectionInfo struct {
|
||||||
Password string
|
Password string
|
||||||
PrivateKey string `mapstructure:"private_key"`
|
PrivateKey string `mapstructure:"private_key"`
|
||||||
Host string
|
Host string
|
||||||
|
HostKey string `mapstructure:"host_key"`
|
||||||
Port int
|
Port int
|
||||||
Agent bool
|
Agent bool
|
||||||
Timeout string
|
Timeout string
|
||||||
|
@ -53,6 +55,7 @@ type connectionInfo struct {
|
||||||
BastionPassword string `mapstructure:"bastion_password"`
|
BastionPassword string `mapstructure:"bastion_password"`
|
||||||
BastionPrivateKey string `mapstructure:"bastion_private_key"`
|
BastionPrivateKey string `mapstructure:"bastion_private_key"`
|
||||||
BastionHost string `mapstructure:"bastion_host"`
|
BastionHost string `mapstructure:"bastion_host"`
|
||||||
|
BastionHostKey string `mapstructure:"bastion_host_key"`
|
||||||
BastionPort int `mapstructure:"bastion_port"`
|
BastionPort int `mapstructure:"bastion_port"`
|
||||||
|
|
||||||
AgentIdentity string `mapstructure:"agent_identity"`
|
AgentIdentity string `mapstructure:"agent_identity"`
|
||||||
|
@ -144,34 +147,38 @@ func prepareSSHConfig(connInfo *connectionInfo) (*sshConfig, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
host := fmt.Sprintf("%s:%d", connInfo.Host, connInfo.Port)
|
||||||
|
|
||||||
sshConf, err := buildSSHClientConfig(sshClientConfigOpts{
|
sshConf, err := buildSSHClientConfig(sshClientConfigOpts{
|
||||||
user: connInfo.User,
|
user: connInfo.User,
|
||||||
|
host: host,
|
||||||
privateKey: connInfo.PrivateKey,
|
privateKey: connInfo.PrivateKey,
|
||||||
password: connInfo.Password,
|
password: connInfo.Password,
|
||||||
|
hostKey: connInfo.HostKey,
|
||||||
sshAgent: sshAgent,
|
sshAgent: sshAgent,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectFunc := ConnectFunc("tcp", host)
|
||||||
|
|
||||||
var bastionConf *ssh.ClientConfig
|
var bastionConf *ssh.ClientConfig
|
||||||
if connInfo.BastionHost != "" {
|
if connInfo.BastionHost != "" {
|
||||||
|
bastionHost := fmt.Sprintf("%s:%d", connInfo.BastionHost, connInfo.BastionPort)
|
||||||
|
|
||||||
bastionConf, err = buildSSHClientConfig(sshClientConfigOpts{
|
bastionConf, err = buildSSHClientConfig(sshClientConfigOpts{
|
||||||
user: connInfo.BastionUser,
|
user: connInfo.BastionUser,
|
||||||
|
host: bastionHost,
|
||||||
privateKey: connInfo.BastionPrivateKey,
|
privateKey: connInfo.BastionPrivateKey,
|
||||||
password: connInfo.BastionPassword,
|
password: connInfo.BastionPassword,
|
||||||
|
hostKey: connInfo.HostKey,
|
||||||
sshAgent: sshAgent,
|
sshAgent: sshAgent,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
host := fmt.Sprintf("%s:%d", connInfo.Host, connInfo.Port)
|
|
||||||
connectFunc := ConnectFunc("tcp", host)
|
|
||||||
|
|
||||||
if bastionConf != nil {
|
|
||||||
bastionHost := fmt.Sprintf("%s:%d", connInfo.BastionHost, connInfo.BastionPort)
|
|
||||||
connectFunc = BastionConnectFunc("tcp", bastionHost, bastionConf, "tcp", host)
|
connectFunc = BastionConnectFunc("tcp", bastionHost, bastionConf, "tcp", host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,11 +195,41 @@ type sshClientConfigOpts struct {
|
||||||
password string
|
password string
|
||||||
sshAgent *sshAgent
|
sshAgent *sshAgent
|
||||||
user string
|
user string
|
||||||
|
host string
|
||||||
|
hostKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSSHClientConfig(opts sshClientConfigOpts) (*ssh.ClientConfig, error) {
|
func buildSSHClientConfig(opts sshClientConfigOpts) (*ssh.ClientConfig, error) {
|
||||||
|
hkCallback := ssh.InsecureIgnoreHostKey()
|
||||||
|
|
||||||
|
if opts.hostKey != "" {
|
||||||
|
// The knownhosts package only takes paths to files, but terraform
|
||||||
|
// generally wants to handle config data in-memory. Rather than making
|
||||||
|
// the known_hosts file an exception, write out the data to a temporary
|
||||||
|
// file to create the HostKeyCallback.
|
||||||
|
tf, err := ioutil.TempFile("", "tf-known_hosts")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create temp known_hosts file: %s", err)
|
||||||
|
}
|
||||||
|
defer tf.Close()
|
||||||
|
defer os.RemoveAll(tf.Name())
|
||||||
|
|
||||||
|
// we mark this as a CA as well, but the host key fallback will still
|
||||||
|
// use it as a direct match if the remote host doesn't return a
|
||||||
|
// certificate.
|
||||||
|
if _, err := tf.WriteString(fmt.Sprintf("@cert-authority %s %s\n", opts.host, opts.hostKey)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to write temp known_hosts file: %s", err)
|
||||||
|
}
|
||||||
|
tf.Sync()
|
||||||
|
|
||||||
|
hkCallback, err = knownhosts.New(tf.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
conf := &ssh.ClientConfig{
|
conf := &ssh.ClientConfig{
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
HostKeyCallback: hkCallback,
|
||||||
User: opts.user,
|
User: opts.user,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -144,8 +144,10 @@ func (n *EvalValidateProvisioner) validateConnConfig(connConfig *ResourceConfig)
|
||||||
|
|
||||||
// For type=ssh only (enforced in ssh communicator)
|
// For type=ssh only (enforced in ssh communicator)
|
||||||
PrivateKey interface{} `mapstructure:"private_key"`
|
PrivateKey interface{} `mapstructure:"private_key"`
|
||||||
|
HostKey interface{} `mapstructure:"host_key"`
|
||||||
Agent interface{} `mapstructure:"agent"`
|
Agent interface{} `mapstructure:"agent"`
|
||||||
BastionHost interface{} `mapstructure:"bastion_host"`
|
BastionHost interface{} `mapstructure:"bastion_host"`
|
||||||
|
BastionHostKey interface{} `mapstructure:"bastion_host_key"`
|
||||||
BastionPort interface{} `mapstructure:"bastion_port"`
|
BastionPort interface{} `mapstructure:"bastion_port"`
|
||||||
BastionUser interface{} `mapstructure:"bastion_user"`
|
BastionUser interface{} `mapstructure:"bastion_user"`
|
||||||
BastionPassword interface{} `mapstructure:"bastion_password"`
|
BastionPassword interface{} `mapstructure:"bastion_password"`
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
// 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 ChaCha20 implements the core ChaCha20 function as specified in https://tools.ietf.org/html/rfc7539#section-2.3.
|
||||||
|
package chacha20
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
const rounds = 20
|
||||||
|
|
||||||
|
// core applies the ChaCha20 core function to 16-byte input in, 32-byte key k,
|
||||||
|
// and 16-byte constant c, and puts the result into 64-byte array out.
|
||||||
|
func core(out *[64]byte, in *[16]byte, k *[32]byte) {
|
||||||
|
j0 := uint32(0x61707865)
|
||||||
|
j1 := uint32(0x3320646e)
|
||||||
|
j2 := uint32(0x79622d32)
|
||||||
|
j3 := uint32(0x6b206574)
|
||||||
|
j4 := binary.LittleEndian.Uint32(k[0:4])
|
||||||
|
j5 := binary.LittleEndian.Uint32(k[4:8])
|
||||||
|
j6 := binary.LittleEndian.Uint32(k[8:12])
|
||||||
|
j7 := binary.LittleEndian.Uint32(k[12:16])
|
||||||
|
j8 := binary.LittleEndian.Uint32(k[16:20])
|
||||||
|
j9 := binary.LittleEndian.Uint32(k[20:24])
|
||||||
|
j10 := binary.LittleEndian.Uint32(k[24:28])
|
||||||
|
j11 := binary.LittleEndian.Uint32(k[28:32])
|
||||||
|
j12 := binary.LittleEndian.Uint32(in[0:4])
|
||||||
|
j13 := binary.LittleEndian.Uint32(in[4:8])
|
||||||
|
j14 := binary.LittleEndian.Uint32(in[8:12])
|
||||||
|
j15 := binary.LittleEndian.Uint32(in[12:16])
|
||||||
|
|
||||||
|
x0, x1, x2, x3, x4, x5, x6, x7 := j0, j1, j2, j3, j4, j5, j6, j7
|
||||||
|
x8, x9, x10, x11, x12, x13, x14, x15 := j8, j9, j10, j11, j12, j13, j14, j15
|
||||||
|
|
||||||
|
for i := 0; i < rounds; i += 2 {
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = (x12 << 16) | (x12 >> (16))
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = (x4 << 12) | (x4 >> (20))
|
||||||
|
x0 += x4
|
||||||
|
x12 ^= x0
|
||||||
|
x12 = (x12 << 8) | (x12 >> (24))
|
||||||
|
x8 += x12
|
||||||
|
x4 ^= x8
|
||||||
|
x4 = (x4 << 7) | (x4 >> (25))
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = (x13 << 16) | (x13 >> 16)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = (x5 << 12) | (x5 >> 20)
|
||||||
|
x1 += x5
|
||||||
|
x13 ^= x1
|
||||||
|
x13 = (x13 << 8) | (x13 >> 24)
|
||||||
|
x9 += x13
|
||||||
|
x5 ^= x9
|
||||||
|
x5 = (x5 << 7) | (x5 >> 25)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = (x14 << 16) | (x14 >> 16)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = (x6 << 12) | (x6 >> 20)
|
||||||
|
x2 += x6
|
||||||
|
x14 ^= x2
|
||||||
|
x14 = (x14 << 8) | (x14 >> 24)
|
||||||
|
x10 += x14
|
||||||
|
x6 ^= x10
|
||||||
|
x6 = (x6 << 7) | (x6 >> 25)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = (x15 << 16) | (x15 >> 16)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = (x7 << 12) | (x7 >> 20)
|
||||||
|
x3 += x7
|
||||||
|
x15 ^= x3
|
||||||
|
x15 = (x15 << 8) | (x15 >> 24)
|
||||||
|
x11 += x15
|
||||||
|
x7 ^= x11
|
||||||
|
x7 = (x7 << 7) | (x7 >> 25)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = (x15 << 16) | (x15 >> 16)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = (x5 << 12) | (x5 >> 20)
|
||||||
|
x0 += x5
|
||||||
|
x15 ^= x0
|
||||||
|
x15 = (x15 << 8) | (x15 >> 24)
|
||||||
|
x10 += x15
|
||||||
|
x5 ^= x10
|
||||||
|
x5 = (x5 << 7) | (x5 >> 25)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = (x12 << 16) | (x12 >> 16)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = (x6 << 12) | (x6 >> 20)
|
||||||
|
x1 += x6
|
||||||
|
x12 ^= x1
|
||||||
|
x12 = (x12 << 8) | (x12 >> 24)
|
||||||
|
x11 += x12
|
||||||
|
x6 ^= x11
|
||||||
|
x6 = (x6 << 7) | (x6 >> 25)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = (x13 << 16) | (x13 >> 16)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = (x7 << 12) | (x7 >> 20)
|
||||||
|
x2 += x7
|
||||||
|
x13 ^= x2
|
||||||
|
x13 = (x13 << 8) | (x13 >> 24)
|
||||||
|
x8 += x13
|
||||||
|
x7 ^= x8
|
||||||
|
x7 = (x7 << 7) | (x7 >> 25)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = (x14 << 16) | (x14 >> 16)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = (x4 << 12) | (x4 >> 20)
|
||||||
|
x3 += x4
|
||||||
|
x14 ^= x3
|
||||||
|
x14 = (x14 << 8) | (x14 >> 24)
|
||||||
|
x9 += x14
|
||||||
|
x4 ^= x9
|
||||||
|
x4 = (x4 << 7) | (x4 >> 25)
|
||||||
|
}
|
||||||
|
|
||||||
|
x0 += j0
|
||||||
|
x1 += j1
|
||||||
|
x2 += j2
|
||||||
|
x3 += j3
|
||||||
|
x4 += j4
|
||||||
|
x5 += j5
|
||||||
|
x6 += j6
|
||||||
|
x7 += j7
|
||||||
|
x8 += j8
|
||||||
|
x9 += j9
|
||||||
|
x10 += j10
|
||||||
|
x11 += j11
|
||||||
|
x12 += j12
|
||||||
|
x13 += j13
|
||||||
|
x14 += j14
|
||||||
|
x15 += j15
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(out[0:4], x0)
|
||||||
|
binary.LittleEndian.PutUint32(out[4:8], x1)
|
||||||
|
binary.LittleEndian.PutUint32(out[8:12], x2)
|
||||||
|
binary.LittleEndian.PutUint32(out[12:16], x3)
|
||||||
|
binary.LittleEndian.PutUint32(out[16:20], x4)
|
||||||
|
binary.LittleEndian.PutUint32(out[20:24], x5)
|
||||||
|
binary.LittleEndian.PutUint32(out[24:28], x6)
|
||||||
|
binary.LittleEndian.PutUint32(out[28:32], x7)
|
||||||
|
binary.LittleEndian.PutUint32(out[32:36], x8)
|
||||||
|
binary.LittleEndian.PutUint32(out[36:40], x9)
|
||||||
|
binary.LittleEndian.PutUint32(out[40:44], x10)
|
||||||
|
binary.LittleEndian.PutUint32(out[44:48], x11)
|
||||||
|
binary.LittleEndian.PutUint32(out[48:52], x12)
|
||||||
|
binary.LittleEndian.PutUint32(out[52:56], x13)
|
||||||
|
binary.LittleEndian.PutUint32(out[56:60], x14)
|
||||||
|
binary.LittleEndian.PutUint32(out[60:64], x15)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XORKeyStream crypts bytes from in to out using the given key and counters.
|
||||||
|
// In and out must overlap entirely or not at all. Counter contains the raw
|
||||||
|
// ChaCha20 counter bytes (i.e. block counter followed by nonce).
|
||||||
|
func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
|
||||||
|
var block [64]byte
|
||||||
|
var counterCopy [16]byte
|
||||||
|
copy(counterCopy[:], counter[:])
|
||||||
|
|
||||||
|
for len(in) >= 64 {
|
||||||
|
core(&block, &counterCopy, key)
|
||||||
|
for i, x := range block {
|
||||||
|
out[i] = in[i] ^ x
|
||||||
|
}
|
||||||
|
u := uint32(1)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
u += uint32(counterCopy[i])
|
||||||
|
counterCopy[i] = byte(u)
|
||||||
|
u >>= 8
|
||||||
|
}
|
||||||
|
in = in[64:]
|
||||||
|
out = out[64:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(in) > 0 {
|
||||||
|
core(&block, &counterCopy, key)
|
||||||
|
for i, v := range in {
|
||||||
|
out[i] = v ^ block[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package poly1305 implements Poly1305 one-time message authentication code as
|
||||||
|
specified in https://cr.yp.to/mac/poly1305-20050329.pdf.
|
||||||
|
|
||||||
|
Poly1305 is a fast, one-time authentication function. It is infeasible for an
|
||||||
|
attacker to generate an authenticator for a message without the key. However, a
|
||||||
|
key must only be used for a single message. Authenticating two different
|
||||||
|
messages with the same key allows an attacker to forge authenticators for other
|
||||||
|
messages with the same key.
|
||||||
|
|
||||||
|
Poly1305 was originally coupled with AES in order to make Poly1305-AES. AES was
|
||||||
|
used with a fixed key in order to generate one-time keys from an nonce.
|
||||||
|
However, in this package AES isn't used and the one-time key is specified
|
||||||
|
directly.
|
||||||
|
*/
|
||||||
|
package poly1305 // import "golang.org/x/crypto/poly1305"
|
||||||
|
|
||||||
|
import "crypto/subtle"
|
||||||
|
|
||||||
|
// TagSize is the size, in bytes, of a poly1305 authenticator.
|
||||||
|
const TagSize = 16
|
||||||
|
|
||||||
|
// Verify returns true if mac is a valid authenticator for m with the given
|
||||||
|
// key.
|
||||||
|
func Verify(mac *[16]byte, m []byte, key *[32]byte) bool {
|
||||||
|
var tmp [16]byte
|
||||||
|
Sum(&tmp, m, key)
|
||||||
|
return subtle.ConstantTimeCompare(tmp[:], mac[:]) == 1
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build amd64,!gccgo,!appengine
|
||||||
|
|
||||||
|
package poly1305
|
||||||
|
|
||||||
|
// This function is implemented in sum_amd64.s
|
||||||
|
//go:noescape
|
||||||
|
func poly1305(out *[16]byte, m *byte, mlen uint64, key *[32]byte)
|
||||||
|
|
||||||
|
// Sum generates an authenticator for m using a one-time key and puts the
|
||||||
|
// 16-byte result into out. Authenticating two different messages with the same
|
||||||
|
// key allows an attacker to forge messages at will.
|
||||||
|
func Sum(out *[16]byte, m []byte, key *[32]byte) {
|
||||||
|
var mPtr *byte
|
||||||
|
if len(m) > 0 {
|
||||||
|
mPtr = &m[0]
|
||||||
|
}
|
||||||
|
poly1305(out, mPtr, uint64(len(m)), key)
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build amd64,!gccgo,!appengine
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
#define POLY1305_ADD(msg, h0, h1, h2) \
|
||||||
|
ADDQ 0(msg), h0; \
|
||||||
|
ADCQ 8(msg), h1; \
|
||||||
|
ADCQ $1, h2; \
|
||||||
|
LEAQ 16(msg), msg
|
||||||
|
|
||||||
|
#define POLY1305_MUL(h0, h1, h2, r0, r1, t0, t1, t2, t3) \
|
||||||
|
MOVQ r0, AX; \
|
||||||
|
MULQ h0; \
|
||||||
|
MOVQ AX, t0; \
|
||||||
|
MOVQ DX, t1; \
|
||||||
|
MOVQ r0, AX; \
|
||||||
|
MULQ h1; \
|
||||||
|
ADDQ AX, t1; \
|
||||||
|
ADCQ $0, DX; \
|
||||||
|
MOVQ r0, t2; \
|
||||||
|
IMULQ h2, t2; \
|
||||||
|
ADDQ DX, t2; \
|
||||||
|
\
|
||||||
|
MOVQ r1, AX; \
|
||||||
|
MULQ h0; \
|
||||||
|
ADDQ AX, t1; \
|
||||||
|
ADCQ $0, DX; \
|
||||||
|
MOVQ DX, h0; \
|
||||||
|
MOVQ r1, t3; \
|
||||||
|
IMULQ h2, t3; \
|
||||||
|
MOVQ r1, AX; \
|
||||||
|
MULQ h1; \
|
||||||
|
ADDQ AX, t2; \
|
||||||
|
ADCQ DX, t3; \
|
||||||
|
ADDQ h0, t2; \
|
||||||
|
ADCQ $0, t3; \
|
||||||
|
\
|
||||||
|
MOVQ t0, h0; \
|
||||||
|
MOVQ t1, h1; \
|
||||||
|
MOVQ t2, h2; \
|
||||||
|
ANDQ $3, h2; \
|
||||||
|
MOVQ t2, t0; \
|
||||||
|
ANDQ $0xFFFFFFFFFFFFFFFC, t0; \
|
||||||
|
ADDQ t0, h0; \
|
||||||
|
ADCQ t3, h1; \
|
||||||
|
ADCQ $0, h2; \
|
||||||
|
SHRQ $2, t3, t2; \
|
||||||
|
SHRQ $2, t3; \
|
||||||
|
ADDQ t2, h0; \
|
||||||
|
ADCQ t3, h1; \
|
||||||
|
ADCQ $0, h2
|
||||||
|
|
||||||
|
DATA ·poly1305Mask<>+0x00(SB)/8, $0x0FFFFFFC0FFFFFFF
|
||||||
|
DATA ·poly1305Mask<>+0x08(SB)/8, $0x0FFFFFFC0FFFFFFC
|
||||||
|
GLOBL ·poly1305Mask<>(SB), RODATA, $16
|
||||||
|
|
||||||
|
// func poly1305(out *[16]byte, m *byte, mlen uint64, key *[32]key)
|
||||||
|
TEXT ·poly1305(SB), $0-32
|
||||||
|
MOVQ out+0(FP), DI
|
||||||
|
MOVQ m+8(FP), SI
|
||||||
|
MOVQ mlen+16(FP), R15
|
||||||
|
MOVQ key+24(FP), AX
|
||||||
|
|
||||||
|
MOVQ 0(AX), R11
|
||||||
|
MOVQ 8(AX), R12
|
||||||
|
ANDQ ·poly1305Mask<>(SB), R11 // r0
|
||||||
|
ANDQ ·poly1305Mask<>+8(SB), R12 // r1
|
||||||
|
XORQ R8, R8 // h0
|
||||||
|
XORQ R9, R9 // h1
|
||||||
|
XORQ R10, R10 // h2
|
||||||
|
|
||||||
|
CMPQ R15, $16
|
||||||
|
JB bytes_between_0_and_15
|
||||||
|
|
||||||
|
loop:
|
||||||
|
POLY1305_ADD(SI, R8, R9, R10)
|
||||||
|
|
||||||
|
multiply:
|
||||||
|
POLY1305_MUL(R8, R9, R10, R11, R12, BX, CX, R13, R14)
|
||||||
|
SUBQ $16, R15
|
||||||
|
CMPQ R15, $16
|
||||||
|
JAE loop
|
||||||
|
|
||||||
|
bytes_between_0_and_15:
|
||||||
|
TESTQ R15, R15
|
||||||
|
JZ done
|
||||||
|
MOVQ $1, BX
|
||||||
|
XORQ CX, CX
|
||||||
|
XORQ R13, R13
|
||||||
|
ADDQ R15, SI
|
||||||
|
|
||||||
|
flush_buffer:
|
||||||
|
SHLQ $8, BX, CX
|
||||||
|
SHLQ $8, BX
|
||||||
|
MOVB -1(SI), R13
|
||||||
|
XORQ R13, BX
|
||||||
|
DECQ SI
|
||||||
|
DECQ R15
|
||||||
|
JNZ flush_buffer
|
||||||
|
|
||||||
|
ADDQ BX, R8
|
||||||
|
ADCQ CX, R9
|
||||||
|
ADCQ $0, R10
|
||||||
|
MOVQ $16, R15
|
||||||
|
JMP multiply
|
||||||
|
|
||||||
|
done:
|
||||||
|
MOVQ R8, AX
|
||||||
|
MOVQ R9, BX
|
||||||
|
SUBQ $0xFFFFFFFFFFFFFFFB, AX
|
||||||
|
SBBQ $0xFFFFFFFFFFFFFFFF, BX
|
||||||
|
SBBQ $3, R10
|
||||||
|
CMOVQCS R8, AX
|
||||||
|
CMOVQCS R9, BX
|
||||||
|
MOVQ key+24(FP), R8
|
||||||
|
ADDQ 16(R8), AX
|
||||||
|
ADCQ 24(R8), BX
|
||||||
|
|
||||||
|
MOVQ AX, 0(DI)
|
||||||
|
MOVQ BX, 8(DI)
|
||||||
|
RET
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build arm,!gccgo,!appengine,!nacl
|
||||||
|
|
||||||
|
package poly1305
|
||||||
|
|
||||||
|
// This function is implemented in sum_arm.s
|
||||||
|
//go:noescape
|
||||||
|
func poly1305_auth_armv6(out *[16]byte, m *byte, mlen uint32, key *[32]byte)
|
||||||
|
|
||||||
|
// Sum generates an authenticator for m using a one-time key and puts the
|
||||||
|
// 16-byte result into out. Authenticating two different messages with the same
|
||||||
|
// key allows an attacker to forge messages at will.
|
||||||
|
func Sum(out *[16]byte, m []byte, key *[32]byte) {
|
||||||
|
var mPtr *byte
|
||||||
|
if len(m) > 0 {
|
||||||
|
mPtr = &m[0]
|
||||||
|
}
|
||||||
|
poly1305_auth_armv6(out, mPtr, uint32(len(m)), key)
|
||||||
|
}
|
|
@ -0,0 +1,427 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build arm,!gccgo,!appengine,!nacl
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// This code was translated into a form compatible with 5a from the public
|
||||||
|
// domain source by Andrew Moon: github.com/floodyberry/poly1305-opt/blob/master/app/extensions/poly1305.
|
||||||
|
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x00(SB)/4, $0x3ffffff
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x04(SB)/4, $0x3ffff03
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x08(SB)/4, $0x3ffc0ff
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x0c(SB)/4, $0x3f03fff
|
||||||
|
DATA ·poly1305_init_constants_armv6<>+0x10(SB)/4, $0x00fffff
|
||||||
|
GLOBL ·poly1305_init_constants_armv6<>(SB), 8, $20
|
||||||
|
|
||||||
|
// Warning: the linker may use R11 to synthesize certain instructions. Please
|
||||||
|
// take care and verify that no synthetic instructions use it.
|
||||||
|
|
||||||
|
TEXT poly1305_init_ext_armv6<>(SB), NOSPLIT, $0
|
||||||
|
// Needs 16 bytes of stack and 64 bytes of space pointed to by R0. (It
|
||||||
|
// might look like it's only 60 bytes of space but the final four bytes
|
||||||
|
// will be written by another function.) We need to skip over four
|
||||||
|
// bytes of stack because that's saving the value of 'g'.
|
||||||
|
ADD $4, R13, R8
|
||||||
|
MOVM.IB [R4-R7], (R8)
|
||||||
|
MOVM.IA.W (R1), [R2-R5]
|
||||||
|
MOVW $·poly1305_init_constants_armv6<>(SB), R7
|
||||||
|
MOVW R2, R8
|
||||||
|
MOVW R2>>26, R9
|
||||||
|
MOVW R3>>20, g
|
||||||
|
MOVW R4>>14, R11
|
||||||
|
MOVW R5>>8, R12
|
||||||
|
ORR R3<<6, R9, R9
|
||||||
|
ORR R4<<12, g, g
|
||||||
|
ORR R5<<18, R11, R11
|
||||||
|
MOVM.IA (R7), [R2-R6]
|
||||||
|
AND R8, R2, R2
|
||||||
|
AND R9, R3, R3
|
||||||
|
AND g, R4, R4
|
||||||
|
AND R11, R5, R5
|
||||||
|
AND R12, R6, R6
|
||||||
|
MOVM.IA.W [R2-R6], (R0)
|
||||||
|
EOR R2, R2, R2
|
||||||
|
EOR R3, R3, R3
|
||||||
|
EOR R4, R4, R4
|
||||||
|
EOR R5, R5, R5
|
||||||
|
EOR R6, R6, R6
|
||||||
|
MOVM.IA.W [R2-R6], (R0)
|
||||||
|
MOVM.IA.W (R1), [R2-R5]
|
||||||
|
MOVM.IA [R2-R6], (R0)
|
||||||
|
ADD $20, R13, R0
|
||||||
|
MOVM.DA (R0), [R4-R7]
|
||||||
|
RET
|
||||||
|
|
||||||
|
#define MOVW_UNALIGNED(Rsrc, Rdst, Rtmp, offset) \
|
||||||
|
MOVBU (offset+0)(Rsrc), Rtmp; \
|
||||||
|
MOVBU Rtmp, (offset+0)(Rdst); \
|
||||||
|
MOVBU (offset+1)(Rsrc), Rtmp; \
|
||||||
|
MOVBU Rtmp, (offset+1)(Rdst); \
|
||||||
|
MOVBU (offset+2)(Rsrc), Rtmp; \
|
||||||
|
MOVBU Rtmp, (offset+2)(Rdst); \
|
||||||
|
MOVBU (offset+3)(Rsrc), Rtmp; \
|
||||||
|
MOVBU Rtmp, (offset+3)(Rdst)
|
||||||
|
|
||||||
|
TEXT poly1305_blocks_armv6<>(SB), NOSPLIT, $0
|
||||||
|
// Needs 24 bytes of stack for saved registers and then 88 bytes of
|
||||||
|
// scratch space after that. We assume that 24 bytes at (R13) have
|
||||||
|
// already been used: four bytes for the link register saved in the
|
||||||
|
// prelude of poly1305_auth_armv6, four bytes for saving the value of g
|
||||||
|
// in that function and 16 bytes of scratch space used around
|
||||||
|
// poly1305_finish_ext_armv6_skip1.
|
||||||
|
ADD $24, R13, R12
|
||||||
|
MOVM.IB [R4-R8, R14], (R12)
|
||||||
|
MOVW R0, 88(R13)
|
||||||
|
MOVW R1, 92(R13)
|
||||||
|
MOVW R2, 96(R13)
|
||||||
|
MOVW R1, R14
|
||||||
|
MOVW R2, R12
|
||||||
|
MOVW 56(R0), R8
|
||||||
|
WORD $0xe1180008 // TST R8, R8 not working see issue 5921
|
||||||
|
EOR R6, R6, R6
|
||||||
|
MOVW.EQ $(1<<24), R6
|
||||||
|
MOVW R6, 84(R13)
|
||||||
|
ADD $116, R13, g
|
||||||
|
MOVM.IA (R0), [R0-R9]
|
||||||
|
MOVM.IA [R0-R4], (g)
|
||||||
|
CMP $16, R12
|
||||||
|
BLO poly1305_blocks_armv6_done
|
||||||
|
|
||||||
|
poly1305_blocks_armv6_mainloop:
|
||||||
|
WORD $0xe31e0003 // TST R14, #3 not working see issue 5921
|
||||||
|
BEQ poly1305_blocks_armv6_mainloop_aligned
|
||||||
|
ADD $100, R13, g
|
||||||
|
MOVW_UNALIGNED(R14, g, R0, 0)
|
||||||
|
MOVW_UNALIGNED(R14, g, R0, 4)
|
||||||
|
MOVW_UNALIGNED(R14, g, R0, 8)
|
||||||
|
MOVW_UNALIGNED(R14, g, R0, 12)
|
||||||
|
MOVM.IA (g), [R0-R3]
|
||||||
|
ADD $16, R14
|
||||||
|
B poly1305_blocks_armv6_mainloop_loaded
|
||||||
|
|
||||||
|
poly1305_blocks_armv6_mainloop_aligned:
|
||||||
|
MOVM.IA.W (R14), [R0-R3]
|
||||||
|
|
||||||
|
poly1305_blocks_armv6_mainloop_loaded:
|
||||||
|
MOVW R0>>26, g
|
||||||
|
MOVW R1>>20, R11
|
||||||
|
MOVW R2>>14, R12
|
||||||
|
MOVW R14, 92(R13)
|
||||||
|
MOVW R3>>8, R4
|
||||||
|
ORR R1<<6, g, g
|
||||||
|
ORR R2<<12, R11, R11
|
||||||
|
ORR R3<<18, R12, R12
|
||||||
|
BIC $0xfc000000, R0, R0
|
||||||
|
BIC $0xfc000000, g, g
|
||||||
|
MOVW 84(R13), R3
|
||||||
|
BIC $0xfc000000, R11, R11
|
||||||
|
BIC $0xfc000000, R12, R12
|
||||||
|
ADD R0, R5, R5
|
||||||
|
ADD g, R6, R6
|
||||||
|
ORR R3, R4, R4
|
||||||
|
ADD R11, R7, R7
|
||||||
|
ADD $116, R13, R14
|
||||||
|
ADD R12, R8, R8
|
||||||
|
ADD R4, R9, R9
|
||||||
|
MOVM.IA (R14), [R0-R4]
|
||||||
|
MULLU R4, R5, (R11, g)
|
||||||
|
MULLU R3, R5, (R14, R12)
|
||||||
|
MULALU R3, R6, (R11, g)
|
||||||
|
MULALU R2, R6, (R14, R12)
|
||||||
|
MULALU R2, R7, (R11, g)
|
||||||
|
MULALU R1, R7, (R14, R12)
|
||||||
|
ADD R4<<2, R4, R4
|
||||||
|
ADD R3<<2, R3, R3
|
||||||
|
MULALU R1, R8, (R11, g)
|
||||||
|
MULALU R0, R8, (R14, R12)
|
||||||
|
MULALU R0, R9, (R11, g)
|
||||||
|
MULALU R4, R9, (R14, R12)
|
||||||
|
MOVW g, 76(R13)
|
||||||
|
MOVW R11, 80(R13)
|
||||||
|
MOVW R12, 68(R13)
|
||||||
|
MOVW R14, 72(R13)
|
||||||
|
MULLU R2, R5, (R11, g)
|
||||||
|
MULLU R1, R5, (R14, R12)
|
||||||
|
MULALU R1, R6, (R11, g)
|
||||||
|
MULALU R0, R6, (R14, R12)
|
||||||
|
MULALU R0, R7, (R11, g)
|
||||||
|
MULALU R4, R7, (R14, R12)
|
||||||
|
ADD R2<<2, R2, R2
|
||||||
|
ADD R1<<2, R1, R1
|
||||||
|
MULALU R4, R8, (R11, g)
|
||||||
|
MULALU R3, R8, (R14, R12)
|
||||||
|
MULALU R3, R9, (R11, g)
|
||||||
|
MULALU R2, R9, (R14, R12)
|
||||||
|
MOVW g, 60(R13)
|
||||||
|
MOVW R11, 64(R13)
|
||||||
|
MOVW R12, 52(R13)
|
||||||
|
MOVW R14, 56(R13)
|
||||||
|
MULLU R0, R5, (R11, g)
|
||||||
|
MULALU R4, R6, (R11, g)
|
||||||
|
MULALU R3, R7, (R11, g)
|
||||||
|
MULALU R2, R8, (R11, g)
|
||||||
|
MULALU R1, R9, (R11, g)
|
||||||
|
ADD $52, R13, R0
|
||||||
|
MOVM.IA (R0), [R0-R7]
|
||||||
|
MOVW g>>26, R12
|
||||||
|
MOVW R4>>26, R14
|
||||||
|
ORR R11<<6, R12, R12
|
||||||
|
ORR R5<<6, R14, R14
|
||||||
|
BIC $0xfc000000, g, g
|
||||||
|
BIC $0xfc000000, R4, R4
|
||||||
|
ADD.S R12, R0, R0
|
||||||
|
ADC $0, R1, R1
|
||||||
|
ADD.S R14, R6, R6
|
||||||
|
ADC $0, R7, R7
|
||||||
|
MOVW R0>>26, R12
|
||||||
|
MOVW R6>>26, R14
|
||||||
|
ORR R1<<6, R12, R12
|
||||||
|
ORR R7<<6, R14, R14
|
||||||
|
BIC $0xfc000000, R0, R0
|
||||||
|
BIC $0xfc000000, R6, R6
|
||||||
|
ADD R14<<2, R14, R14
|
||||||
|
ADD.S R12, R2, R2
|
||||||
|
ADC $0, R3, R3
|
||||||
|
ADD R14, g, g
|
||||||
|
MOVW R2>>26, R12
|
||||||
|
MOVW g>>26, R14
|
||||||
|
ORR R3<<6, R12, R12
|
||||||
|
BIC $0xfc000000, g, R5
|
||||||
|
BIC $0xfc000000, R2, R7
|
||||||
|
ADD R12, R4, R4
|
||||||
|
ADD R14, R0, R0
|
||||||
|
MOVW R4>>26, R12
|
||||||
|
BIC $0xfc000000, R4, R8
|
||||||
|
ADD R12, R6, R9
|
||||||
|
MOVW 96(R13), R12
|
||||||
|
MOVW 92(R13), R14
|
||||||
|
MOVW R0, R6
|
||||||
|
CMP $32, R12
|
||||||
|
SUB $16, R12, R12
|
||||||
|
MOVW R12, 96(R13)
|
||||||
|
BHS poly1305_blocks_armv6_mainloop
|
||||||
|
|
||||||
|
poly1305_blocks_armv6_done:
|
||||||
|
MOVW 88(R13), R12
|
||||||
|
MOVW R5, 20(R12)
|
||||||
|
MOVW R6, 24(R12)
|
||||||
|
MOVW R7, 28(R12)
|
||||||
|
MOVW R8, 32(R12)
|
||||||
|
MOVW R9, 36(R12)
|
||||||
|
ADD $48, R13, R0
|
||||||
|
MOVM.DA (R0), [R4-R8, R14]
|
||||||
|
RET
|
||||||
|
|
||||||
|
#define MOVHUP_UNALIGNED(Rsrc, Rdst, Rtmp) \
|
||||||
|
MOVBU.P 1(Rsrc), Rtmp; \
|
||||||
|
MOVBU.P Rtmp, 1(Rdst); \
|
||||||
|
MOVBU.P 1(Rsrc), Rtmp; \
|
||||||
|
MOVBU.P Rtmp, 1(Rdst)
|
||||||
|
|
||||||
|
#define MOVWP_UNALIGNED(Rsrc, Rdst, Rtmp) \
|
||||||
|
MOVHUP_UNALIGNED(Rsrc, Rdst, Rtmp); \
|
||||||
|
MOVHUP_UNALIGNED(Rsrc, Rdst, Rtmp)
|
||||||
|
|
||||||
|
// func poly1305_auth_armv6(out *[16]byte, m *byte, mlen uint32, key *[32]key)
|
||||||
|
TEXT ·poly1305_auth_armv6(SB), $196-16
|
||||||
|
// The value 196, just above, is the sum of 64 (the size of the context
|
||||||
|
// structure) and 132 (the amount of stack needed).
|
||||||
|
//
|
||||||
|
// At this point, the stack pointer (R13) has been moved down. It
|
||||||
|
// points to the saved link register and there's 196 bytes of free
|
||||||
|
// space above it.
|
||||||
|
//
|
||||||
|
// The stack for this function looks like:
|
||||||
|
//
|
||||||
|
// +---------------------
|
||||||
|
// |
|
||||||
|
// | 64 bytes of context structure
|
||||||
|
// |
|
||||||
|
// +---------------------
|
||||||
|
// |
|
||||||
|
// | 112 bytes for poly1305_blocks_armv6
|
||||||
|
// |
|
||||||
|
// +---------------------
|
||||||
|
// | 16 bytes of final block, constructed at
|
||||||
|
// | poly1305_finish_ext_armv6_skip8
|
||||||
|
// +---------------------
|
||||||
|
// | four bytes of saved 'g'
|
||||||
|
// +---------------------
|
||||||
|
// | lr, saved by prelude <- R13 points here
|
||||||
|
// +---------------------
|
||||||
|
MOVW g, 4(R13)
|
||||||
|
|
||||||
|
MOVW out+0(FP), R4
|
||||||
|
MOVW m+4(FP), R5
|
||||||
|
MOVW mlen+8(FP), R6
|
||||||
|
MOVW key+12(FP), R7
|
||||||
|
|
||||||
|
ADD $136, R13, R0 // 136 = 4 + 4 + 16 + 112
|
||||||
|
MOVW R7, R1
|
||||||
|
|
||||||
|
// poly1305_init_ext_armv6 will write to the stack from R13+4, but
|
||||||
|
// that's ok because none of the other values have been written yet.
|
||||||
|
BL poly1305_init_ext_armv6<>(SB)
|
||||||
|
BIC.S $15, R6, R2
|
||||||
|
BEQ poly1305_auth_armv6_noblocks
|
||||||
|
ADD $136, R13, R0
|
||||||
|
MOVW R5, R1
|
||||||
|
ADD R2, R5, R5
|
||||||
|
SUB R2, R6, R6
|
||||||
|
BL poly1305_blocks_armv6<>(SB)
|
||||||
|
|
||||||
|
poly1305_auth_armv6_noblocks:
|
||||||
|
ADD $136, R13, R0
|
||||||
|
MOVW R5, R1
|
||||||
|
MOVW R6, R2
|
||||||
|
MOVW R4, R3
|
||||||
|
|
||||||
|
MOVW R0, R5
|
||||||
|
MOVW R1, R6
|
||||||
|
MOVW R2, R7
|
||||||
|
MOVW R3, R8
|
||||||
|
AND.S R2, R2, R2
|
||||||
|
BEQ poly1305_finish_ext_armv6_noremaining
|
||||||
|
EOR R0, R0
|
||||||
|
ADD $8, R13, R9 // 8 = offset to 16 byte scratch space
|
||||||
|
MOVW R0, (R9)
|
||||||
|
MOVW R0, 4(R9)
|
||||||
|
MOVW R0, 8(R9)
|
||||||
|
MOVW R0, 12(R9)
|
||||||
|
WORD $0xe3110003 // TST R1, #3 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_aligned
|
||||||
|
WORD $0xe3120008 // TST R2, #8 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip8
|
||||||
|
MOVWP_UNALIGNED(R1, R9, g)
|
||||||
|
MOVWP_UNALIGNED(R1, R9, g)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip8:
|
||||||
|
WORD $0xe3120004 // TST $4, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip4
|
||||||
|
MOVWP_UNALIGNED(R1, R9, g)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip4:
|
||||||
|
WORD $0xe3120002 // TST $2, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip2
|
||||||
|
MOVHUP_UNALIGNED(R1, R9, g)
|
||||||
|
B poly1305_finish_ext_armv6_skip2
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_aligned:
|
||||||
|
WORD $0xe3120008 // TST R2, #8 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip8_aligned
|
||||||
|
MOVM.IA.W (R1), [g-R11]
|
||||||
|
MOVM.IA.W [g-R11], (R9)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip8_aligned:
|
||||||
|
WORD $0xe3120004 // TST $4, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip4_aligned
|
||||||
|
MOVW.P 4(R1), g
|
||||||
|
MOVW.P g, 4(R9)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip4_aligned:
|
||||||
|
WORD $0xe3120002 // TST $2, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip2
|
||||||
|
MOVHU.P 2(R1), g
|
||||||
|
MOVH.P g, 2(R9)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip2:
|
||||||
|
WORD $0xe3120001 // TST $1, R2 not working see issue 5921
|
||||||
|
BEQ poly1305_finish_ext_armv6_skip1
|
||||||
|
MOVBU.P 1(R1), g
|
||||||
|
MOVBU.P g, 1(R9)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_skip1:
|
||||||
|
MOVW $1, R11
|
||||||
|
MOVBU R11, 0(R9)
|
||||||
|
MOVW R11, 56(R5)
|
||||||
|
MOVW R5, R0
|
||||||
|
ADD $8, R13, R1
|
||||||
|
MOVW $16, R2
|
||||||
|
BL poly1305_blocks_armv6<>(SB)
|
||||||
|
|
||||||
|
poly1305_finish_ext_armv6_noremaining:
|
||||||
|
MOVW 20(R5), R0
|
||||||
|
MOVW 24(R5), R1
|
||||||
|
MOVW 28(R5), R2
|
||||||
|
MOVW 32(R5), R3
|
||||||
|
MOVW 36(R5), R4
|
||||||
|
MOVW R4>>26, R12
|
||||||
|
BIC $0xfc000000, R4, R4
|
||||||
|
ADD R12<<2, R12, R12
|
||||||
|
ADD R12, R0, R0
|
||||||
|
MOVW R0>>26, R12
|
||||||
|
BIC $0xfc000000, R0, R0
|
||||||
|
ADD R12, R1, R1
|
||||||
|
MOVW R1>>26, R12
|
||||||
|
BIC $0xfc000000, R1, R1
|
||||||
|
ADD R12, R2, R2
|
||||||
|
MOVW R2>>26, R12
|
||||||
|
BIC $0xfc000000, R2, R2
|
||||||
|
ADD R12, R3, R3
|
||||||
|
MOVW R3>>26, R12
|
||||||
|
BIC $0xfc000000, R3, R3
|
||||||
|
ADD R12, R4, R4
|
||||||
|
ADD $5, R0, R6
|
||||||
|
MOVW R6>>26, R12
|
||||||
|
BIC $0xfc000000, R6, R6
|
||||||
|
ADD R12, R1, R7
|
||||||
|
MOVW R7>>26, R12
|
||||||
|
BIC $0xfc000000, R7, R7
|
||||||
|
ADD R12, R2, g
|
||||||
|
MOVW g>>26, R12
|
||||||
|
BIC $0xfc000000, g, g
|
||||||
|
ADD R12, R3, R11
|
||||||
|
MOVW $-(1<<26), R12
|
||||||
|
ADD R11>>26, R12, R12
|
||||||
|
BIC $0xfc000000, R11, R11
|
||||||
|
ADD R12, R4, R9
|
||||||
|
MOVW R9>>31, R12
|
||||||
|
SUB $1, R12
|
||||||
|
AND R12, R6, R6
|
||||||
|
AND R12, R7, R7
|
||||||
|
AND R12, g, g
|
||||||
|
AND R12, R11, R11
|
||||||
|
AND R12, R9, R9
|
||||||
|
MVN R12, R12
|
||||||
|
AND R12, R0, R0
|
||||||
|
AND R12, R1, R1
|
||||||
|
AND R12, R2, R2
|
||||||
|
AND R12, R3, R3
|
||||||
|
AND R12, R4, R4
|
||||||
|
ORR R6, R0, R0
|
||||||
|
ORR R7, R1, R1
|
||||||
|
ORR g, R2, R2
|
||||||
|
ORR R11, R3, R3
|
||||||
|
ORR R9, R4, R4
|
||||||
|
ORR R1<<26, R0, R0
|
||||||
|
MOVW R1>>6, R1
|
||||||
|
ORR R2<<20, R1, R1
|
||||||
|
MOVW R2>>12, R2
|
||||||
|
ORR R3<<14, R2, R2
|
||||||
|
MOVW R3>>18, R3
|
||||||
|
ORR R4<<8, R3, R3
|
||||||
|
MOVW 40(R5), R6
|
||||||
|
MOVW 44(R5), R7
|
||||||
|
MOVW 48(R5), g
|
||||||
|
MOVW 52(R5), R11
|
||||||
|
ADD.S R6, R0, R0
|
||||||
|
ADC.S R7, R1, R1
|
||||||
|
ADC.S g, R2, R2
|
||||||
|
ADC.S R11, R3, R3
|
||||||
|
MOVM.IA [R0-R3], (R8)
|
||||||
|
MOVW R5, R12
|
||||||
|
EOR R0, R0, R0
|
||||||
|
EOR R1, R1, R1
|
||||||
|
EOR R2, R2, R2
|
||||||
|
EOR R3, R3, R3
|
||||||
|
EOR R4, R4, R4
|
||||||
|
EOR R5, R5, R5
|
||||||
|
EOR R6, R6, R6
|
||||||
|
EOR R7, R7, R7
|
||||||
|
MOVM.IA.W [R0-R7], (R12)
|
||||||
|
MOVM.IA [R0-R7], (R12)
|
||||||
|
MOVW 4(R13), g
|
||||||
|
RET
|
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !amd64,!arm gccgo appengine nacl
|
||||||
|
|
||||||
|
package poly1305
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
// Sum generates an authenticator for msg using a one-time key and puts the
|
||||||
|
// 16-byte result into out. Authenticating two different messages with the same
|
||||||
|
// key allows an attacker to forge messages at will.
|
||||||
|
func Sum(out *[TagSize]byte, msg []byte, key *[32]byte) {
|
||||||
|
var (
|
||||||
|
h0, h1, h2, h3, h4 uint32 // the hash accumulators
|
||||||
|
r0, r1, r2, r3, r4 uint64 // the r part of the key
|
||||||
|
)
|
||||||
|
|
||||||
|
r0 = uint64(binary.LittleEndian.Uint32(key[0:]) & 0x3ffffff)
|
||||||
|
r1 = uint64((binary.LittleEndian.Uint32(key[3:]) >> 2) & 0x3ffff03)
|
||||||
|
r2 = uint64((binary.LittleEndian.Uint32(key[6:]) >> 4) & 0x3ffc0ff)
|
||||||
|
r3 = uint64((binary.LittleEndian.Uint32(key[9:]) >> 6) & 0x3f03fff)
|
||||||
|
r4 = uint64((binary.LittleEndian.Uint32(key[12:]) >> 8) & 0x00fffff)
|
||||||
|
|
||||||
|
R1, R2, R3, R4 := r1*5, r2*5, r3*5, r4*5
|
||||||
|
|
||||||
|
for len(msg) >= TagSize {
|
||||||
|
// h += msg
|
||||||
|
h0 += binary.LittleEndian.Uint32(msg[0:]) & 0x3ffffff
|
||||||
|
h1 += (binary.LittleEndian.Uint32(msg[3:]) >> 2) & 0x3ffffff
|
||||||
|
h2 += (binary.LittleEndian.Uint32(msg[6:]) >> 4) & 0x3ffffff
|
||||||
|
h3 += (binary.LittleEndian.Uint32(msg[9:]) >> 6) & 0x3ffffff
|
||||||
|
h4 += (binary.LittleEndian.Uint32(msg[12:]) >> 8) | (1 << 24)
|
||||||
|
|
||||||
|
// h *= r
|
||||||
|
d0 := (uint64(h0) * r0) + (uint64(h1) * R4) + (uint64(h2) * R3) + (uint64(h3) * R2) + (uint64(h4) * R1)
|
||||||
|
d1 := (d0 >> 26) + (uint64(h0) * r1) + (uint64(h1) * r0) + (uint64(h2) * R4) + (uint64(h3) * R3) + (uint64(h4) * R2)
|
||||||
|
d2 := (d1 >> 26) + (uint64(h0) * r2) + (uint64(h1) * r1) + (uint64(h2) * r0) + (uint64(h3) * R4) + (uint64(h4) * R3)
|
||||||
|
d3 := (d2 >> 26) + (uint64(h0) * r3) + (uint64(h1) * r2) + (uint64(h2) * r1) + (uint64(h3) * r0) + (uint64(h4) * R4)
|
||||||
|
d4 := (d3 >> 26) + (uint64(h0) * r4) + (uint64(h1) * r3) + (uint64(h2) * r2) + (uint64(h3) * r1) + (uint64(h4) * r0)
|
||||||
|
|
||||||
|
// h %= p
|
||||||
|
h0 = uint32(d0) & 0x3ffffff
|
||||||
|
h1 = uint32(d1) & 0x3ffffff
|
||||||
|
h2 = uint32(d2) & 0x3ffffff
|
||||||
|
h3 = uint32(d3) & 0x3ffffff
|
||||||
|
h4 = uint32(d4) & 0x3ffffff
|
||||||
|
|
||||||
|
h0 += uint32(d4>>26) * 5
|
||||||
|
h1 += h0 >> 26
|
||||||
|
h0 = h0 & 0x3ffffff
|
||||||
|
|
||||||
|
msg = msg[TagSize:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(msg) > 0 {
|
||||||
|
var block [TagSize]byte
|
||||||
|
off := copy(block[:], msg)
|
||||||
|
block[off] = 0x01
|
||||||
|
|
||||||
|
// h += msg
|
||||||
|
h0 += binary.LittleEndian.Uint32(block[0:]) & 0x3ffffff
|
||||||
|
h1 += (binary.LittleEndian.Uint32(block[3:]) >> 2) & 0x3ffffff
|
||||||
|
h2 += (binary.LittleEndian.Uint32(block[6:]) >> 4) & 0x3ffffff
|
||||||
|
h3 += (binary.LittleEndian.Uint32(block[9:]) >> 6) & 0x3ffffff
|
||||||
|
h4 += (binary.LittleEndian.Uint32(block[12:]) >> 8)
|
||||||
|
|
||||||
|
// h *= r
|
||||||
|
d0 := (uint64(h0) * r0) + (uint64(h1) * R4) + (uint64(h2) * R3) + (uint64(h3) * R2) + (uint64(h4) * R1)
|
||||||
|
d1 := (d0 >> 26) + (uint64(h0) * r1) + (uint64(h1) * r0) + (uint64(h2) * R4) + (uint64(h3) * R3) + (uint64(h4) * R2)
|
||||||
|
d2 := (d1 >> 26) + (uint64(h0) * r2) + (uint64(h1) * r1) + (uint64(h2) * r0) + (uint64(h3) * R4) + (uint64(h4) * R3)
|
||||||
|
d3 := (d2 >> 26) + (uint64(h0) * r3) + (uint64(h1) * r2) + (uint64(h2) * r1) + (uint64(h3) * r0) + (uint64(h4) * R4)
|
||||||
|
d4 := (d3 >> 26) + (uint64(h0) * r4) + (uint64(h1) * r3) + (uint64(h2) * r2) + (uint64(h3) * r1) + (uint64(h4) * r0)
|
||||||
|
|
||||||
|
// h %= p
|
||||||
|
h0 = uint32(d0) & 0x3ffffff
|
||||||
|
h1 = uint32(d1) & 0x3ffffff
|
||||||
|
h2 = uint32(d2) & 0x3ffffff
|
||||||
|
h3 = uint32(d3) & 0x3ffffff
|
||||||
|
h4 = uint32(d4) & 0x3ffffff
|
||||||
|
|
||||||
|
h0 += uint32(d4>>26) * 5
|
||||||
|
h1 += h0 >> 26
|
||||||
|
h0 = h0 & 0x3ffffff
|
||||||
|
}
|
||||||
|
|
||||||
|
// h %= p reduction
|
||||||
|
h2 += h1 >> 26
|
||||||
|
h1 &= 0x3ffffff
|
||||||
|
h3 += h2 >> 26
|
||||||
|
h2 &= 0x3ffffff
|
||||||
|
h4 += h3 >> 26
|
||||||
|
h3 &= 0x3ffffff
|
||||||
|
h0 += 5 * (h4 >> 26)
|
||||||
|
h4 &= 0x3ffffff
|
||||||
|
h1 += h0 >> 26
|
||||||
|
h0 &= 0x3ffffff
|
||||||
|
|
||||||
|
// h - p
|
||||||
|
t0 := h0 + 5
|
||||||
|
t1 := h1 + (t0 >> 26)
|
||||||
|
t2 := h2 + (t1 >> 26)
|
||||||
|
t3 := h3 + (t2 >> 26)
|
||||||
|
t4 := h4 + (t3 >> 26) - (1 << 26)
|
||||||
|
t0 &= 0x3ffffff
|
||||||
|
t1 &= 0x3ffffff
|
||||||
|
t2 &= 0x3ffffff
|
||||||
|
t3 &= 0x3ffffff
|
||||||
|
|
||||||
|
// select h if h < p else h - p
|
||||||
|
t_mask := (t4 >> 31) - 1
|
||||||
|
h_mask := ^t_mask
|
||||||
|
h0 = (h0 & h_mask) | (t0 & t_mask)
|
||||||
|
h1 = (h1 & h_mask) | (t1 & t_mask)
|
||||||
|
h2 = (h2 & h_mask) | (t2 & t_mask)
|
||||||
|
h3 = (h3 & h_mask) | (t3 & t_mask)
|
||||||
|
h4 = (h4 & h_mask) | (t4 & t_mask)
|
||||||
|
|
||||||
|
// h %= 2^128
|
||||||
|
h0 |= h1 << 26
|
||||||
|
h1 = ((h1 >> 6) | (h2 << 20))
|
||||||
|
h2 = ((h2 >> 12) | (h3 << 14))
|
||||||
|
h3 = ((h3 >> 18) | (h4 << 8))
|
||||||
|
|
||||||
|
// s: the s part of the key
|
||||||
|
// tag = (h + s) % (2^128)
|
||||||
|
t := uint64(h0) + uint64(binary.LittleEndian.Uint32(key[16:]))
|
||||||
|
h0 = uint32(t)
|
||||||
|
t = uint64(h1) + uint64(binary.LittleEndian.Uint32(key[20:])) + (t >> 32)
|
||||||
|
h1 = uint32(t)
|
||||||
|
t = uint64(h2) + uint64(binary.LittleEndian.Uint32(key[24:])) + (t >> 32)
|
||||||
|
h2 = uint32(t)
|
||||||
|
t = uint64(h3) + uint64(binary.LittleEndian.Uint32(key[28:])) + (t >> 32)
|
||||||
|
h3 = uint32(t)
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(out[0:], h0)
|
||||||
|
binary.LittleEndian.PutUint32(out[4:], h1)
|
||||||
|
binary.LittleEndian.PutUint32(out[8:], h2)
|
||||||
|
binary.LittleEndian.PutUint32(out[12:], h3)
|
||||||
|
}
|
|
@ -44,7 +44,9 @@ type Signature struct {
|
||||||
const CertTimeInfinity = 1<<64 - 1
|
const CertTimeInfinity = 1<<64 - 1
|
||||||
|
|
||||||
// An Certificate represents an OpenSSH certificate as defined in
|
// An Certificate represents an OpenSSH certificate as defined in
|
||||||
// [PROTOCOL.certkeys]?rev=1.8.
|
// [PROTOCOL.certkeys]?rev=1.8. The Certificate type implements the
|
||||||
|
// PublicKey interface, so it can be unmarshaled using
|
||||||
|
// ParsePublicKey.
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
Nonce []byte
|
Nonce []byte
|
||||||
Key PublicKey
|
Key PublicKey
|
||||||
|
@ -340,7 +342,7 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
|
||||||
// the signature of the certificate.
|
// the signature of the certificate.
|
||||||
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||||
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
||||||
return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial)
|
return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial)
|
||||||
}
|
}
|
||||||
|
|
||||||
for opt := range cert.CriticalOptions {
|
for opt := range cert.CriticalOptions {
|
||||||
|
|
|
@ -16,6 +16,9 @@ import (
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/internal/chacha20"
|
||||||
|
"golang.org/x/crypto/poly1305"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -53,32 +56,25 @@ func newRC4(key, iv []byte) (cipher.Stream, error) {
|
||||||
return rc4.NewCipher(key)
|
return rc4.NewCipher(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamCipherMode struct {
|
type cipherMode struct {
|
||||||
keySize int
|
keySize int
|
||||||
ivSize int
|
ivSize int
|
||||||
skip int
|
create func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error)
|
||||||
createFunc func(key, iv []byte) (cipher.Stream, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
|
func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream, error)) func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
if len(key) < c.keySize {
|
return func(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
panic("ssh: key length too small for cipher")
|
stream, err := createFunc(key, iv)
|
||||||
}
|
|
||||||
if len(iv) < c.ivSize {
|
|
||||||
panic("ssh: iv too small for cipher")
|
|
||||||
}
|
|
||||||
|
|
||||||
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var streamDump []byte
|
var streamDump []byte
|
||||||
if c.skip > 0 {
|
if skip > 0 {
|
||||||
streamDump = make([]byte, 512)
|
streamDump = make([]byte, 512)
|
||||||
}
|
}
|
||||||
|
|
||||||
for remainingToDump := c.skip; remainingToDump > 0; {
|
for remainingToDump := skip; remainingToDump > 0; {
|
||||||
dumpThisTime := remainingToDump
|
dumpThisTime := remainingToDump
|
||||||
if dumpThisTime > len(streamDump) {
|
if dumpThisTime > len(streamDump) {
|
||||||
dumpThisTime = len(streamDump)
|
dumpThisTime = len(streamDump)
|
||||||
|
@ -87,44 +83,51 @@ func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
|
||||||
remainingToDump -= dumpThisTime
|
remainingToDump -= dumpThisTime
|
||||||
}
|
}
|
||||||
|
|
||||||
return stream, nil
|
mac := macModes[algs.MAC].new(macKey)
|
||||||
|
return &streamPacketCipher{
|
||||||
|
mac: mac,
|
||||||
|
etm: macModes[algs.MAC].etm,
|
||||||
|
macResult: make([]byte, mac.Size()),
|
||||||
|
cipher: stream,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cipherModes documents properties of supported ciphers. Ciphers not included
|
// cipherModes documents properties of supported ciphers. Ciphers not included
|
||||||
// are not supported and will not be negotiated, even if explicitly requested in
|
// are not supported and will not be negotiated, even if explicitly requested in
|
||||||
// ClientConfig.Crypto.Ciphers.
|
// ClientConfig.Crypto.Ciphers.
|
||||||
var cipherModes = map[string]*streamCipherMode{
|
var cipherModes = map[string]*cipherMode{
|
||||||
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
||||||
// are defined in the order specified in the RFC.
|
// are defined in the order specified in the RFC.
|
||||||
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
|
"aes128-ctr": {16, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
||||||
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
|
"aes192-ctr": {24, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
||||||
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
|
"aes256-ctr": {32, aes.BlockSize, streamCipherMode(0, newAESCTR)},
|
||||||
|
|
||||||
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
||||||
// They are defined in the order specified in the RFC.
|
// They are defined in the order specified in the RFC.
|
||||||
"arcfour128": {16, 0, 1536, newRC4},
|
"arcfour128": {16, 0, streamCipherMode(1536, newRC4)},
|
||||||
"arcfour256": {32, 0, 1536, newRC4},
|
"arcfour256": {32, 0, streamCipherMode(1536, newRC4)},
|
||||||
|
|
||||||
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
||||||
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
||||||
// RC4) has problems with weak keys, and should be used with caution."
|
// RC4) has problems with weak keys, and should be used with caution."
|
||||||
// RFC4345 introduces improved versions of Arcfour.
|
// RFC4345 introduces improved versions of Arcfour.
|
||||||
"arcfour": {16, 0, 0, newRC4},
|
"arcfour": {16, 0, streamCipherMode(0, newRC4)},
|
||||||
|
|
||||||
// AES-GCM is not a stream cipher, so it is constructed with a
|
// AEAD ciphers
|
||||||
// special case. If we add any more non-stream ciphers, we
|
gcmCipherID: {16, 12, newGCMCipher},
|
||||||
// should invest a cleaner way to do this.
|
chacha20Poly1305ID: {64, 0, newChaCha20Cipher},
|
||||||
gcmCipherID: {16, 12, 0, nil},
|
|
||||||
|
|
||||||
// CBC mode is insecure and so is not included in the default config.
|
// CBC mode is insecure and so is not included in the default config.
|
||||||
// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
|
// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
|
||||||
// needed, it's possible to specify a custom Config to enable it.
|
// needed, it's possible to specify a custom Config to enable it.
|
||||||
// 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, newAESCBCCipher},
|
||||||
|
|
||||||
// 3des-cbc is insecure and is disabled by default.
|
// 3des-cbc is insecure and is not included in the default
|
||||||
tripledescbcID: {24, des.BlockSize, 0, nil},
|
// config.
|
||||||
|
tripledescbcID: {24, des.BlockSize, newTripleDESCBCCipher},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -304,7 +307,7 @@ type gcmCipher struct {
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGCMCipher(iv, key []byte) (packetCipher, error) {
|
func newGCMCipher(key, iv, unusedMacKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
|
||||||
c, err := aes.NewCipher(key)
|
c, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -422,7 +425,7 @@ type cbcCipher struct {
|
||||||
oracleCamouflage uint32
|
oracleCamouflage uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
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),
|
||||||
|
@ -436,13 +439,13 @@ func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorith
|
||||||
return cbc, nil
|
return cbc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
func newAESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
c, err := aes.NewCipher(key)
|
c, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
cbc, err := newCBCCipher(c, key, iv, macKey, algs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -450,13 +453,13 @@ func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCi
|
||||||
return cbc, nil
|
return cbc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
func newTripleDESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||||
c, err := des.NewTripleDESCipher(key)
|
c, err := des.NewTripleDESCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
cbc, err := newCBCCipher(c, key, iv, macKey, algs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -627,3 +630,142 @@ func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, pack
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chacha20Poly1305ID = "chacha20-poly1305@openssh.com"
|
||||||
|
|
||||||
|
// chacha20Poly1305Cipher implements the chacha20-poly1305@openssh.com
|
||||||
|
// AEAD, which is described here:
|
||||||
|
//
|
||||||
|
// https://tools.ietf.org/html/draft-josefsson-ssh-chacha20-poly1305-openssh-00
|
||||||
|
//
|
||||||
|
// the methods here also implement padding, which RFC4253 Section 6
|
||||||
|
// also requires of stream ciphers.
|
||||||
|
type chacha20Poly1305Cipher struct {
|
||||||
|
lengthKey [32]byte
|
||||||
|
contentKey [32]byte
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChaCha20Cipher(key, unusedIV, unusedMACKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
|
||||||
|
if len(key) != 64 {
|
||||||
|
panic(len(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &chacha20Poly1305Cipher{
|
||||||
|
buf: make([]byte, 256),
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(c.contentKey[:], key[:32])
|
||||||
|
copy(c.lengthKey[:], key[32:])
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Poly1305 key is obtained by encrypting 32 0-bytes.
|
||||||
|
var chacha20PolyKeyInput [32]byte
|
||||||
|
|
||||||
|
func (c *chacha20Poly1305Cipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||||
|
var counter [16]byte
|
||||||
|
binary.BigEndian.PutUint64(counter[8:], uint64(seqNum))
|
||||||
|
|
||||||
|
var polyKey [32]byte
|
||||||
|
chacha20.XORKeyStream(polyKey[:], chacha20PolyKeyInput[:], &counter, &c.contentKey)
|
||||||
|
|
||||||
|
encryptedLength := c.buf[:4]
|
||||||
|
if _, err := io.ReadFull(r, encryptedLength); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var lenBytes [4]byte
|
||||||
|
chacha20.XORKeyStream(lenBytes[:], encryptedLength, &counter, &c.lengthKey)
|
||||||
|
|
||||||
|
length := binary.BigEndian.Uint32(lenBytes[:])
|
||||||
|
if length > maxPacket {
|
||||||
|
return nil, errors.New("ssh: invalid packet length, packet too large")
|
||||||
|
}
|
||||||
|
|
||||||
|
contentEnd := 4 + length
|
||||||
|
packetEnd := contentEnd + poly1305.TagSize
|
||||||
|
if uint32(cap(c.buf)) < packetEnd {
|
||||||
|
c.buf = make([]byte, packetEnd)
|
||||||
|
copy(c.buf[:], encryptedLength)
|
||||||
|
} else {
|
||||||
|
c.buf = c.buf[:packetEnd]
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(r, c.buf[4:packetEnd]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var mac [poly1305.TagSize]byte
|
||||||
|
copy(mac[:], c.buf[contentEnd:packetEnd])
|
||||||
|
if !poly1305.Verify(&mac, c.buf[:contentEnd], &polyKey) {
|
||||||
|
return nil, errors.New("ssh: MAC failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
counter[0] = 1
|
||||||
|
|
||||||
|
plain := c.buf[4:contentEnd]
|
||||||
|
chacha20.XORKeyStream(plain, plain, &counter, &c.contentKey)
|
||||||
|
|
||||||
|
padding := plain[0]
|
||||||
|
if padding < 4 {
|
||||||
|
// padding is a byte, so it automatically satisfies
|
||||||
|
// the maximum size, which is 255.
|
||||||
|
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(padding)+1 >= len(plain) {
|
||||||
|
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
plain = plain[1 : len(plain)-int(padding)]
|
||||||
|
|
||||||
|
return plain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *chacha20Poly1305Cipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, payload []byte) error {
|
||||||
|
var counter [16]byte
|
||||||
|
binary.BigEndian.PutUint64(counter[8:], uint64(seqNum))
|
||||||
|
|
||||||
|
var polyKey [32]byte
|
||||||
|
chacha20.XORKeyStream(polyKey[:], chacha20PolyKeyInput[:], &counter, &c.contentKey)
|
||||||
|
|
||||||
|
// There is no blocksize, so fall back to multiple of 8 byte
|
||||||
|
// padding, as described in RFC 4253, Sec 6.
|
||||||
|
const packetSizeMultiple = 8
|
||||||
|
|
||||||
|
padding := packetSizeMultiple - (1+len(payload))%packetSizeMultiple
|
||||||
|
if padding < 4 {
|
||||||
|
padding += packetSizeMultiple
|
||||||
|
}
|
||||||
|
|
||||||
|
// size (4 bytes), padding (1), payload, padding, tag.
|
||||||
|
totalLength := 4 + 1 + len(payload) + padding + poly1305.TagSize
|
||||||
|
if cap(c.buf) < totalLength {
|
||||||
|
c.buf = make([]byte, totalLength)
|
||||||
|
} else {
|
||||||
|
c.buf = c.buf[:totalLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(c.buf, uint32(1+len(payload)+padding))
|
||||||
|
chacha20.XORKeyStream(c.buf, c.buf[:4], &counter, &c.lengthKey)
|
||||||
|
c.buf[4] = byte(padding)
|
||||||
|
copy(c.buf[5:], payload)
|
||||||
|
packetEnd := 5 + len(payload) + padding
|
||||||
|
if _, err := io.ReadFull(rand, c.buf[5+len(payload):packetEnd]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
counter[0] = 1
|
||||||
|
chacha20.XORKeyStream(c.buf[4:], c.buf[4:packetEnd], &counter, &c.contentKey)
|
||||||
|
|
||||||
|
var mac [poly1305.TagSize]byte
|
||||||
|
poly1305.Sum(&mac, c.buf[:packetEnd], &polyKey)
|
||||||
|
|
||||||
|
copy(c.buf[packetEnd:], mac[:])
|
||||||
|
|
||||||
|
if _, err := w.Write(c.buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,14 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type authResult int
|
||||||
|
|
||||||
|
const (
|
||||||
|
authFailure authResult = iota
|
||||||
|
authPartialSuccess
|
||||||
|
authSuccess
|
||||||
|
)
|
||||||
|
|
||||||
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
||||||
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||||
// initiate user auth session
|
// initiate user auth session
|
||||||
|
@ -37,11 +45,12 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if ok {
|
if ok == authSuccess {
|
||||||
// success
|
// success
|
||||||
return nil
|
return nil
|
||||||
}
|
} else if ok == authFailure {
|
||||||
tried[auth.method()] = true
|
tried[auth.method()] = true
|
||||||
|
}
|
||||||
if methods == nil {
|
if methods == nil {
|
||||||
methods = lastMethods
|
methods = lastMethods
|
||||||
}
|
}
|
||||||
|
@ -82,7 +91,7 @@ type AuthMethod interface {
|
||||||
// If authentication is not successful, a []string of alternative
|
// If authentication is not successful, a []string of alternative
|
||||||
// method names is returned. If the slice is nil, it will be ignored
|
// method names is returned. If the slice is nil, it will be ignored
|
||||||
// and the previous set of possible methods will be reused.
|
// and the previous set of possible methods will be reused.
|
||||||
auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
|
auth(session []byte, user string, p packetConn, rand io.Reader) (authResult, []string, error)
|
||||||
|
|
||||||
// method returns the RFC 4252 method name.
|
// method returns the RFC 4252 method name.
|
||||||
method() string
|
method() string
|
||||||
|
@ -91,13 +100,13 @@ type AuthMethod interface {
|
||||||
// "none" authentication, RFC 4252 section 5.2.
|
// "none" authentication, RFC 4252 section 5.2.
|
||||||
type noneAuth int
|
type noneAuth int
|
||||||
|
|
||||||
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
|
||||||
if err := c.writePacket(Marshal(&userAuthRequestMsg{
|
if err := c.writePacket(Marshal(&userAuthRequestMsg{
|
||||||
User: user,
|
User: user,
|
||||||
Service: serviceSSH,
|
Service: serviceSSH,
|
||||||
Method: "none",
|
Method: "none",
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleAuthResponse(c)
|
return handleAuthResponse(c)
|
||||||
|
@ -111,7 +120,7 @@ func (n *noneAuth) method() string {
|
||||||
// a function call, e.g. by prompting the user.
|
// a function call, e.g. by prompting the user.
|
||||||
type passwordCallback func() (password string, err error)
|
type passwordCallback func() (password string, err error)
|
||||||
|
|
||||||
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
|
||||||
type passwordAuthMsg struct {
|
type passwordAuthMsg struct {
|
||||||
User string `sshtype:"50"`
|
User string `sshtype:"50"`
|
||||||
Service string
|
Service string
|
||||||
|
@ -125,7 +134,7 @@ func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
// The program may only find out that the user doesn't have a password
|
// The program may only find out that the user doesn't have a password
|
||||||
// when prompting.
|
// when prompting.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.writePacket(Marshal(&passwordAuthMsg{
|
if err := c.writePacket(Marshal(&passwordAuthMsg{
|
||||||
|
@ -135,7 +144,7 @@ func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
Reply: false,
|
Reply: false,
|
||||||
Password: pw,
|
Password: pw,
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleAuthResponse(c)
|
return handleAuthResponse(c)
|
||||||
|
@ -178,7 +187,7 @@ func (cb publicKeyCallback) method() string {
|
||||||
return "publickey"
|
return "publickey"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
|
||||||
// Authentication is performed by sending an enquiry to test if a key is
|
// Authentication is performed by sending an enquiry to test if a key is
|
||||||
// acceptable to the remote. If the key is acceptable, the client will
|
// acceptable to the remote. If the key is acceptable, the client will
|
||||||
// attempt to authenticate with the valid key. If not the client will repeat
|
// attempt to authenticate with the valid key. If not the client will repeat
|
||||||
|
@ -186,13 +195,13 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
|
|
||||||
signers, err := cb()
|
signers, err := cb()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
var methods []string
|
var methods []string
|
||||||
for _, signer := range signers {
|
for _, signer := range signers {
|
||||||
ok, err := validateKey(signer.PublicKey(), user, c)
|
ok, err := validateKey(signer.PublicKey(), user, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
|
@ -206,7 +215,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
Method: cb.method(),
|
Method: cb.method(),
|
||||||
}, []byte(pub.Type()), pubKey))
|
}, []byte(pub.Type()), pubKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// manually wrap the serialized signature in a string
|
// manually wrap the serialized signature in a string
|
||||||
|
@ -224,24 +233,24 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
||||||
}
|
}
|
||||||
p := Marshal(&msg)
|
p := Marshal(&msg)
|
||||||
if err := c.writePacket(p); err != nil {
|
if err := c.writePacket(p); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
var success bool
|
var success authResult
|
||||||
success, methods, err = handleAuthResponse(c)
|
success, methods, err = handleAuthResponse(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If authentication succeeds or the list of available methods does not
|
// If authentication succeeds or the list of available methods does not
|
||||||
// contain the "publickey" method, do not attempt to authenticate with any
|
// contain the "publickey" method, do not attempt to authenticate with any
|
||||||
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
||||||
// additional authentication methods are required.
|
// additional authentication methods are required.
|
||||||
if success || !containsMethod(methods, cb.method()) {
|
if success == authSuccess || !containsMethod(methods, cb.method()) {
|
||||||
return success, methods, err
|
return success, methods, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, methods, nil
|
return authFailure, methods, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsMethod(methods []string, method string) bool {
|
func containsMethod(methods []string, method string) bool {
|
||||||
|
@ -318,28 +327,31 @@ func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMet
|
||||||
// handleAuthResponse returns whether the preceding authentication request succeeded
|
// handleAuthResponse returns whether the preceding authentication request succeeded
|
||||||
// along with a list of remaining authentication methods to try next and
|
// along with a list of remaining authentication methods to try next and
|
||||||
// an error if an unexpected response was received.
|
// an error if an unexpected response was received.
|
||||||
func handleAuthResponse(c packetConn) (bool, []string, error) {
|
func handleAuthResponse(c packetConn) (authResult, []string, error) {
|
||||||
for {
|
for {
|
||||||
packet, err := c.readPacket()
|
packet, err := c.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch packet[0] {
|
switch packet[0] {
|
||||||
case msgUserAuthBanner:
|
case msgUserAuthBanner:
|
||||||
if err := handleBannerResponse(c, packet); err != nil {
|
if err := handleBannerResponse(c, packet); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
case msgUserAuthFailure:
|
case msgUserAuthFailure:
|
||||||
var msg userAuthFailureMsg
|
var msg userAuthFailureMsg
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
if err := Unmarshal(packet, &msg); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
return false, msg.Methods, nil
|
if msg.PartialSuccess {
|
||||||
|
return authPartialSuccess, msg.Methods, nil
|
||||||
|
}
|
||||||
|
return authFailure, msg.Methods, nil
|
||||||
case msgUserAuthSuccess:
|
case msgUserAuthSuccess:
|
||||||
return true, nil, nil
|
return authSuccess, nil, nil
|
||||||
default:
|
default:
|
||||||
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,7 +393,7 @@ func (cb KeyboardInteractiveChallenge) method() string {
|
||||||
return "keyboard-interactive"
|
return "keyboard-interactive"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
|
||||||
type initiateMsg struct {
|
type initiateMsg struct {
|
||||||
User string `sshtype:"50"`
|
User string `sshtype:"50"`
|
||||||
Service string
|
Service string
|
||||||
|
@ -395,20 +407,20 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
Service: serviceSSH,
|
Service: serviceSSH,
|
||||||
Method: "keyboard-interactive",
|
Method: "keyboard-interactive",
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
packet, err := c.readPacket()
|
packet, err := c.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// like handleAuthResponse, but with less options.
|
// like handleAuthResponse, but with less options.
|
||||||
switch packet[0] {
|
switch packet[0] {
|
||||||
case msgUserAuthBanner:
|
case msgUserAuthBanner:
|
||||||
if err := handleBannerResponse(c, packet); err != nil {
|
if err := handleBannerResponse(c, packet); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case msgUserAuthInfoRequest:
|
case msgUserAuthInfoRequest:
|
||||||
|
@ -416,18 +428,21 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
case msgUserAuthFailure:
|
case msgUserAuthFailure:
|
||||||
var msg userAuthFailureMsg
|
var msg userAuthFailureMsg
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
if err := Unmarshal(packet, &msg); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
return false, msg.Methods, nil
|
if msg.PartialSuccess {
|
||||||
|
return authPartialSuccess, msg.Methods, nil
|
||||||
|
}
|
||||||
|
return authFailure, msg.Methods, nil
|
||||||
case msgUserAuthSuccess:
|
case msgUserAuthSuccess:
|
||||||
return true, nil, nil
|
return authSuccess, nil, nil
|
||||||
default:
|
default:
|
||||||
return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
|
return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg userAuthInfoRequestMsg
|
var msg userAuthInfoRequestMsg
|
||||||
if err := Unmarshal(packet, &msg); err != nil {
|
if err := Unmarshal(packet, &msg); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually unpack the prompt/echo pairs.
|
// Manually unpack the prompt/echo pairs.
|
||||||
|
@ -437,7 +452,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
for i := 0; i < int(msg.NumPrompts); i++ {
|
for i := 0; i < int(msg.NumPrompts); i++ {
|
||||||
prompt, r, ok := parseString(rest)
|
prompt, r, ok := parseString(rest)
|
||||||
if !ok || len(r) == 0 {
|
if !ok || len(r) == 0 {
|
||||||
return false, nil, errors.New("ssh: prompt format error")
|
return authFailure, nil, errors.New("ssh: prompt format error")
|
||||||
}
|
}
|
||||||
prompts = append(prompts, string(prompt))
|
prompts = append(prompts, string(prompt))
|
||||||
echos = append(echos, r[0] != 0)
|
echos = append(echos, r[0] != 0)
|
||||||
|
@ -445,16 +460,16 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rest) != 0 {
|
if len(rest) != 0 {
|
||||||
return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
|
return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
|
||||||
}
|
}
|
||||||
|
|
||||||
answers, err := cb(msg.User, msg.Instruction, prompts, echos)
|
answers, err := cb(msg.User, msg.Instruction, prompts, echos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(answers) != len(prompts) {
|
if len(answers) != len(prompts) {
|
||||||
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
return authFailure, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
||||||
}
|
}
|
||||||
responseLength := 1 + 4
|
responseLength := 1 + 4
|
||||||
for _, a := range answers {
|
for _, a := range answers {
|
||||||
|
@ -470,7 +485,7 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.writePacket(serialized); err != nil {
|
if err := c.writePacket(serialized); err != nil {
|
||||||
return false, nil, err
|
return authFailure, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -480,10 +495,10 @@ type retryableAuthMethod struct {
|
||||||
maxTries int
|
maxTries int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) {
|
func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok authResult, methods []string, err error) {
|
||||||
for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
|
for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
|
||||||
ok, methods, err = r.authMethod.auth(session, user, c, rand)
|
ok, methods, err = r.authMethod.auth(session, user, c, rand)
|
||||||
if ok || err != nil { // either success or error terminate
|
if ok != authFailure || err != nil { // either success, partial success or error terminate
|
||||||
return ok, methods, err
|
return ok, methods, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,21 @@ const (
|
||||||
serviceSSH = "ssh-connection"
|
serviceSSH = "ssh-connection"
|
||||||
)
|
)
|
||||||
|
|
||||||
// supportedCiphers specifies the supported ciphers in preference order.
|
// supportedCiphers lists ciphers we support but might not recommend.
|
||||||
var supportedCiphers = []string{
|
var supportedCiphers = []string{
|
||||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||||
"aes128-gcm@openssh.com",
|
"aes128-gcm@openssh.com",
|
||||||
"arcfour256", "arcfour128",
|
chacha20Poly1305ID,
|
||||||
|
"arcfour256", "arcfour128", "arcfour",
|
||||||
|
aes128cbcID,
|
||||||
|
tripledescbcID,
|
||||||
|
}
|
||||||
|
|
||||||
|
// preferredCiphers specifies the default preference for ciphers.
|
||||||
|
var preferredCiphers = []string{
|
||||||
|
"aes128-gcm@openssh.com",
|
||||||
|
chacha20Poly1305ID,
|
||||||
|
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||||
}
|
}
|
||||||
|
|
||||||
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
||||||
|
@ -211,7 +221,7 @@ func (c *Config) SetDefaults() {
|
||||||
c.Rand = rand.Reader
|
c.Rand = rand.Reader
|
||||||
}
|
}
|
||||||
if c.Ciphers == nil {
|
if c.Ciphers == nil {
|
||||||
c.Ciphers = supportedCiphers
|
c.Ciphers = preferredCiphers
|
||||||
}
|
}
|
||||||
var ciphers []string
|
var ciphers []string
|
||||||
for _, c := range c.Ciphers {
|
for _, c := range c.Ciphers {
|
||||||
|
|
|
@ -0,0 +1,546 @@
|
||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package knownhosts implements a parser for the OpenSSH
|
||||||
|
// known_hosts host key database.
|
||||||
|
package knownhosts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// See the sshd manpage
|
||||||
|
// (http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) for
|
||||||
|
// background.
|
||||||
|
|
||||||
|
type addr struct{ host, port string }
|
||||||
|
|
||||||
|
func (a *addr) String() string {
|
||||||
|
h := a.host
|
||||||
|
if strings.Contains(h, ":") {
|
||||||
|
h = "[" + h + "]"
|
||||||
|
}
|
||||||
|
return h + ":" + a.port
|
||||||
|
}
|
||||||
|
|
||||||
|
type matcher interface {
|
||||||
|
match([]addr) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type hostPattern struct {
|
||||||
|
negate bool
|
||||||
|
addr addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hostPattern) String() string {
|
||||||
|
n := ""
|
||||||
|
if p.negate {
|
||||||
|
n = "!"
|
||||||
|
}
|
||||||
|
|
||||||
|
return n + p.addr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type hostPatterns []hostPattern
|
||||||
|
|
||||||
|
func (ps hostPatterns) match(addrs []addr) bool {
|
||||||
|
matched := false
|
||||||
|
for _, p := range ps {
|
||||||
|
for _, a := range addrs {
|
||||||
|
m := p.match(a)
|
||||||
|
if !m {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p.negate {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
matched = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
// See
|
||||||
|
// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/addrmatch.c
|
||||||
|
// The matching of * has no regard for separators, unlike filesystem globs
|
||||||
|
func wildcardMatch(pat []byte, str []byte) bool {
|
||||||
|
for {
|
||||||
|
if len(pat) == 0 {
|
||||||
|
return len(str) == 0
|
||||||
|
}
|
||||||
|
if len(str) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if pat[0] == '*' {
|
||||||
|
if len(pat) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := range str {
|
||||||
|
if wildcardMatch(pat[1:], str[j:]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if pat[0] == '?' || pat[0] == str[0] {
|
||||||
|
pat = pat[1:]
|
||||||
|
str = str[1:]
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hostPattern) match(a addr) bool {
|
||||||
|
return wildcardMatch([]byte(p.addr.host), []byte(a.host)) && p.addr.port == a.port
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyDBLine struct {
|
||||||
|
cert bool
|
||||||
|
matcher matcher
|
||||||
|
knownKey KnownKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialize(k ssh.PublicKey) string {
|
||||||
|
return k.Type() + " " + base64.StdEncoding.EncodeToString(k.Marshal())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *keyDBLine) match(addrs []addr) bool {
|
||||||
|
return l.matcher.match(addrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
type hostKeyDB struct {
|
||||||
|
// Serialized version of revoked keys
|
||||||
|
revoked map[string]*KnownKey
|
||||||
|
lines []keyDBLine
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHostKeyDB() *hostKeyDB {
|
||||||
|
db := &hostKeyDB{
|
||||||
|
revoked: make(map[string]*KnownKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyEq(a, b ssh.PublicKey) bool {
|
||||||
|
return bytes.Equal(a.Marshal(), b.Marshal())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthorityForHost can be used as a callback in ssh.CertChecker
|
||||||
|
func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
|
||||||
|
h, p, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
a := addr{host: h, port: p}
|
||||||
|
|
||||||
|
for _, l := range db.lines {
|
||||||
|
if l.cert && keyEq(l.knownKey.Key, remote) && l.match([]addr{a}) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRevoked can be used as a callback in ssh.CertChecker
|
||||||
|
func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
|
||||||
|
_, ok := db.revoked[string(key.Marshal())]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
const markerCert = "@cert-authority"
|
||||||
|
const markerRevoked = "@revoked"
|
||||||
|
|
||||||
|
func nextWord(line []byte) (string, []byte) {
|
||||||
|
i := bytes.IndexAny(line, "\t ")
|
||||||
|
if i == -1 {
|
||||||
|
return string(line), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(line[:i]), bytes.TrimSpace(line[i:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
|
||||||
|
if w, next := nextWord(line); w == markerCert || w == markerRevoked {
|
||||||
|
marker = w
|
||||||
|
line = next
|
||||||
|
}
|
||||||
|
|
||||||
|
host, line = nextWord(line)
|
||||||
|
if len(line) == 0 {
|
||||||
|
return "", "", nil, errors.New("knownhosts: missing host pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the keytype as it's in the key blob anyway.
|
||||||
|
_, line = nextWord(line)
|
||||||
|
if len(line) == 0 {
|
||||||
|
return "", "", nil, errors.New("knownhosts: missing key type pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBlob, _ := nextWord(line)
|
||||||
|
|
||||||
|
keyBytes, err := base64.StdEncoding.DecodeString(keyBlob)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
key, err = ssh.ParsePublicKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return marker, host, key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error {
|
||||||
|
marker, pattern, key, err := parseLine(line)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if marker == markerRevoked {
|
||||||
|
db.revoked[string(key.Marshal())] = &KnownKey{
|
||||||
|
Key: key,
|
||||||
|
Filename: filename,
|
||||||
|
Line: linenum,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := keyDBLine{
|
||||||
|
cert: marker == markerCert,
|
||||||
|
knownKey: KnownKey{
|
||||||
|
Filename: filename,
|
||||||
|
Line: linenum,
|
||||||
|
Key: key,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if pattern[0] == '|' {
|
||||||
|
entry.matcher, err = newHashedHost(pattern)
|
||||||
|
} else {
|
||||||
|
entry.matcher, err = newHostnameMatcher(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
db.lines = append(db.lines, entry)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHostnameMatcher(pattern string) (matcher, error) {
|
||||||
|
var hps hostPatterns
|
||||||
|
for _, p := range strings.Split(pattern, ",") {
|
||||||
|
if len(p) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var a addr
|
||||||
|
var negate bool
|
||||||
|
if p[0] == '!' {
|
||||||
|
negate = true
|
||||||
|
p = p[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p) == 0 {
|
||||||
|
return nil, errors.New("knownhosts: negation without following hostname")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if p[0] == '[' {
|
||||||
|
a.host, a.port, err = net.SplitHostPort(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a.host, a.port, err = net.SplitHostPort(p)
|
||||||
|
if err != nil {
|
||||||
|
a.host = p
|
||||||
|
a.port = "22"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hps = append(hps, hostPattern{
|
||||||
|
negate: negate,
|
||||||
|
addr: a,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return hps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KnownKey represents a key declared in a known_hosts file.
|
||||||
|
type KnownKey struct {
|
||||||
|
Key ssh.PublicKey
|
||||||
|
Filename string
|
||||||
|
Line int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KnownKey) String() string {
|
||||||
|
return fmt.Sprintf("%s:%d: %s", k.Filename, k.Line, serialize(k.Key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyError is returned if we did not find the key in the host key
|
||||||
|
// database, or there was a mismatch. Typically, in batch
|
||||||
|
// applications, this should be interpreted as failure. Interactive
|
||||||
|
// applications can offer an interactive prompt to the user.
|
||||||
|
type KeyError struct {
|
||||||
|
// Want holds the accepted host keys. For each key algorithm,
|
||||||
|
// there can be one hostkey. If Want is empty, the host is
|
||||||
|
// unknown. If Want is non-empty, there was a mismatch, which
|
||||||
|
// can signify a MITM attack.
|
||||||
|
Want []KnownKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *KeyError) Error() string {
|
||||||
|
if len(u.Want) == 0 {
|
||||||
|
return "knownhosts: key is unknown"
|
||||||
|
}
|
||||||
|
return "knownhosts: key mismatch"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokedError is returned if we found a key that was revoked.
|
||||||
|
type RevokedError struct {
|
||||||
|
Revoked KnownKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RevokedError) Error() string {
|
||||||
|
return "knownhosts: key is revoked"
|
||||||
|
}
|
||||||
|
|
||||||
|
// check checks a key against the host database. This should not be
|
||||||
|
// used for verifying certificates.
|
||||||
|
func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
|
||||||
|
if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
|
||||||
|
return &RevokedError{Revoked: *revoked}
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(remote.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", remote, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs := []addr{
|
||||||
|
{host, port},
|
||||||
|
}
|
||||||
|
|
||||||
|
if address != "" {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", address, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs = append(addrs, addr{host, port})
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.checkAddrs(addrs, remoteKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkAddrs checks if we can find the given public key for any of
|
||||||
|
// the given addresses. If we only find an entry for the IP address,
|
||||||
|
// or only the hostname, then this still succeeds.
|
||||||
|
func (db *hostKeyDB) checkAddrs(addrs []addr, remoteKey ssh.PublicKey) error {
|
||||||
|
// TODO(hanwen): are these the right semantics? What if there
|
||||||
|
// is just a key for the IP address, but not for the
|
||||||
|
// hostname?
|
||||||
|
|
||||||
|
// Algorithm => key.
|
||||||
|
knownKeys := map[string]KnownKey{}
|
||||||
|
for _, l := range db.lines {
|
||||||
|
if l.match(addrs) {
|
||||||
|
typ := l.knownKey.Key.Type()
|
||||||
|
if _, ok := knownKeys[typ]; !ok {
|
||||||
|
knownKeys[typ] = l.knownKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyErr := &KeyError{}
|
||||||
|
for _, v := range knownKeys {
|
||||||
|
keyErr.Want = append(keyErr.Want, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown remote host.
|
||||||
|
if len(knownKeys) == 0 {
|
||||||
|
return keyErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the remote host starts using a different, unknown key type, we
|
||||||
|
// also interpret that as a mismatch.
|
||||||
|
if known, ok := knownKeys[remoteKey.Type()]; !ok || !keyEq(known.Key, remoteKey) {
|
||||||
|
return keyErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Read function parses file contents.
|
||||||
|
func (db *hostKeyDB) Read(r io.Reader, filename string) error {
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
|
||||||
|
lineNum := 0
|
||||||
|
for scanner.Scan() {
|
||||||
|
lineNum++
|
||||||
|
line := scanner.Bytes()
|
||||||
|
line = bytes.TrimSpace(line)
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.parseLine(line, filename, lineNum); err != nil {
|
||||||
|
return fmt.Errorf("knownhosts: %s:%d: %v", filename, lineNum, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scanner.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a host key callback from the given OpenSSH host key
|
||||||
|
// files. The returned callback is for use in
|
||||||
|
// ssh.ClientConfig.HostKeyCallback.
|
||||||
|
func New(files ...string) (ssh.HostKeyCallback, error) {
|
||||||
|
db := newHostKeyDB()
|
||||||
|
for _, fn := range files {
|
||||||
|
f, err := os.Open(fn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if err := db.Read(f, fn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var certChecker ssh.CertChecker
|
||||||
|
certChecker.IsHostAuthority = db.IsHostAuthority
|
||||||
|
certChecker.IsRevoked = db.IsRevoked
|
||||||
|
certChecker.HostKeyFallback = db.check
|
||||||
|
|
||||||
|
return certChecker.CheckHostKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize normalizes an address into the form used in known_hosts
|
||||||
|
func Normalize(address string) string {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
host = address
|
||||||
|
port = "22"
|
||||||
|
}
|
||||||
|
entry := host
|
||||||
|
if port != "22" {
|
||||||
|
entry = "[" + entry + "]:" + port
|
||||||
|
} else if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
|
||||||
|
entry = "[" + entry + "]"
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line returns a line to add append to the known_hosts files.
|
||||||
|
func Line(addresses []string, key ssh.PublicKey) string {
|
||||||
|
var trimmed []string
|
||||||
|
for _, a := range addresses {
|
||||||
|
trimmed = append(trimmed, Normalize(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(trimmed, ",") + " " + serialize(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashHostname hashes the given hostname. The hostname is not
|
||||||
|
// normalized before hashing.
|
||||||
|
func HashHostname(hostname string) string {
|
||||||
|
// TODO(hanwen): check if we can safely normalize this always.
|
||||||
|
salt := make([]byte, sha1.Size)
|
||||||
|
|
||||||
|
_, err := rand.Read(salt)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("crypto/rand failure %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := hashHost(hostname, salt)
|
||||||
|
return encodeHash(sha1HashType, salt, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHash(encoded string) (hashType string, salt, hash []byte, err error) {
|
||||||
|
if len(encoded) == 0 || encoded[0] != '|' {
|
||||||
|
err = errors.New("knownhosts: hashed host must start with '|'")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
components := strings.Split(encoded, "|")
|
||||||
|
if len(components) != 4 {
|
||||||
|
err = fmt.Errorf("knownhosts: got %d components, want 3", len(components))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hashType = components[1]
|
||||||
|
if salt, err = base64.StdEncoding.DecodeString(components[2]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if hash, err = base64.StdEncoding.DecodeString(components[3]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeHash(typ string, salt []byte, hash []byte) string {
|
||||||
|
return strings.Join([]string{"",
|
||||||
|
typ,
|
||||||
|
base64.StdEncoding.EncodeToString(salt),
|
||||||
|
base64.StdEncoding.EncodeToString(hash),
|
||||||
|
}, "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
|
||||||
|
func hashHost(hostname string, salt []byte) []byte {
|
||||||
|
mac := hmac.New(sha1.New, salt)
|
||||||
|
mac.Write([]byte(hostname))
|
||||||
|
return mac.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type hashedHost struct {
|
||||||
|
salt []byte
|
||||||
|
hash []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const sha1HashType = "1"
|
||||||
|
|
||||||
|
func newHashedHost(encoded string) (*hashedHost, error) {
|
||||||
|
typ, salt, hash, err := decodeHash(encoded)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type field seems for future algorithm agility, but it's
|
||||||
|
// actually hardcoded in openssh currently, see
|
||||||
|
// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
|
||||||
|
if typ != sha1HashType {
|
||||||
|
return nil, fmt.Errorf("knownhosts: got hash type %s, must be '1'", typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &hashedHost{salt: salt, hash: hash}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hashedHost) match(addrs []addr) bool {
|
||||||
|
for _, a := range addrs {
|
||||||
|
if bytes.Equal(hashHost(Normalize(a.String()), h.salt), h.hash) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
@ -232,52 +233,22 @@ var (
|
||||||
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
|
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
|
||||||
)
|
)
|
||||||
|
|
||||||
// generateKeys generates key material for IV, MAC and encryption.
|
|
||||||
func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) {
|
|
||||||
cipherMode := cipherModes[algs.Cipher]
|
|
||||||
macMode := macModes[algs.MAC]
|
|
||||||
|
|
||||||
iv = make([]byte, cipherMode.ivSize)
|
|
||||||
key = make([]byte, cipherMode.keySize)
|
|
||||||
macKey = make([]byte, macMode.keySize)
|
|
||||||
|
|
||||||
generateKeyMaterial(iv, d.ivTag, kex)
|
|
||||||
generateKeyMaterial(key, d.keyTag, kex)
|
|
||||||
generateKeyMaterial(macKey, d.macKeyTag, kex)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
|
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
|
||||||
// described in RFC 4253, section 6.4. direction should either be serverKeys
|
// described in RFC 4253, section 6.4. direction should either be serverKeys
|
||||||
// (to setup server->client keys) or clientKeys (for client->server keys).
|
// (to setup server->client keys) or clientKeys (for client->server keys).
|
||||||
func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
|
func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) {
|
||||||
iv, key, macKey := generateKeys(d, algs, kex)
|
cipherMode := cipherModes[algs.Cipher]
|
||||||
|
macMode := macModes[algs.MAC]
|
||||||
|
|
||||||
if algs.Cipher == gcmCipherID {
|
iv := make([]byte, cipherMode.ivSize)
|
||||||
return newGCMCipher(iv, key)
|
key := make([]byte, cipherMode.keySize)
|
||||||
}
|
macKey := make([]byte, macMode.keySize)
|
||||||
|
|
||||||
if algs.Cipher == aes128cbcID {
|
generateKeyMaterial(iv, d.ivTag, kex)
|
||||||
return newAESCBCCipher(iv, key, macKey, algs)
|
generateKeyMaterial(key, d.keyTag, kex)
|
||||||
}
|
generateKeyMaterial(macKey, d.macKeyTag, kex)
|
||||||
|
|
||||||
if algs.Cipher == tripledescbcID {
|
return cipherModes[algs.Cipher].create(key, iv, macKey, algs)
|
||||||
return newTripleDESCBCCipher(iv, key, macKey, algs)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &streamPacketCipher{
|
|
||||||
mac: macModes[algs.MAC].new(macKey),
|
|
||||||
etm: macModes[algs.MAC].etm,
|
|
||||||
}
|
|
||||||
c.macResult = make([]byte, c.mac.Size())
|
|
||||||
|
|
||||||
var err error
|
|
||||||
c.cipher, err = cipherModes[algs.Cipher].createStream(key, iv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateKeyMaterial fills out with key material generated from tag, K, H
|
// generateKeyMaterial fills out with key material generated from tag, K, H
|
||||||
|
@ -342,7 +313,7 @@ func readVersion(r io.Reader) ([]byte, error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
var buf [1]byte
|
var buf [1]byte
|
||||||
|
|
||||||
for len(versionString) < maxVersionStringBytes {
|
for length := 0; length < maxVersionStringBytes; length++ {
|
||||||
_, err := io.ReadFull(r, buf[:])
|
_, err := io.ReadFull(r, buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -350,6 +321,13 @@ func readVersion(r io.Reader) ([]byte, error) {
|
||||||
// The RFC says that the version should be terminated with \r\n
|
// The RFC says that the version should be terminated with \r\n
|
||||||
// but several SSH servers actually only send a \n.
|
// but several SSH servers actually only send a \n.
|
||||||
if buf[0] == '\n' {
|
if buf[0] == '\n' {
|
||||||
|
if !bytes.HasPrefix(versionString, []byte("SSH-")) {
|
||||||
|
// RFC 4253 says we need to ignore all version string lines
|
||||||
|
// except the one containing the SSH version (provided that
|
||||||
|
// all the lines do not exceed 255 bytes in total).
|
||||||
|
versionString = versionString[:0]
|
||||||
|
continue
|
||||||
|
}
|
||||||
ok = true
|
ok = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -2263,6 +2263,12 @@
|
||||||
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
|
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
|
||||||
"revisionTime": "2017-02-08T20:51:15Z"
|
"revisionTime": "2017-02-08T20:51:15Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "hfABw6DX9B4Ma+88qDDGz9qY45s=",
|
||||||
|
"path": "golang.org/x/crypto/internal/chacha20",
|
||||||
|
"revision": "9de5f2eaf759b4c4550b3db39fed2e9e5f86f45c",
|
||||||
|
"revisionTime": "2018-02-11T11:39:43Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "MCeXr2RNeiG1XG6V+er1OR0qyeo=",
|
"checksumSHA1": "MCeXr2RNeiG1XG6V+er1OR0qyeo=",
|
||||||
"path": "golang.org/x/crypto/md4",
|
"path": "golang.org/x/crypto/md4",
|
||||||
|
@ -2306,10 +2312,16 @@
|
||||||
"revisionTime": "2017-02-08T20:51:15Z"
|
"revisionTime": "2017-02-08T20:51:15Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "pySTR3iSeU7FhjbBnQPNxgyIa4I=",
|
"checksumSHA1": "kVKE0OX1Xdw5mG7XKT86DLLKE2I=",
|
||||||
|
"path": "golang.org/x/crypto/poly1305",
|
||||||
|
"revision": "9de5f2eaf759b4c4550b3db39fed2e9e5f86f45c",
|
||||||
|
"revisionTime": "2018-02-11T11:39:43Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "ZK4HWtg3hJzayz0RRcc6qHpkums=",
|
||||||
"path": "golang.org/x/crypto/ssh",
|
"path": "golang.org/x/crypto/ssh",
|
||||||
"revision": "d585fd2cc9195196078f516b69daff6744ef5e84",
|
"revision": "9de5f2eaf759b4c4550b3db39fed2e9e5f86f45c",
|
||||||
"revisionTime": "2017-12-16T04:08:15Z",
|
"revisionTime": "2018-02-11T11:39:43Z",
|
||||||
"version": "master",
|
"version": "master",
|
||||||
"versionExact": "master"
|
"versionExact": "master"
|
||||||
},
|
},
|
||||||
|
@ -2321,6 +2333,14 @@
|
||||||
"version": "master",
|
"version": "master",
|
||||||
"versionExact": "master"
|
"versionExact": "master"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "He3gVAAa2cym6sK3f2tgZoCMdOk=",
|
||||||
|
"path": "golang.org/x/crypto/ssh/knownhosts",
|
||||||
|
"revision": "650f4a345ab4e5b245a3034b110ebc7299e68186",
|
||||||
|
"revisionTime": "2017-09-27T09:16:38Z",
|
||||||
|
"version": "master",
|
||||||
|
"versionExact": "master"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
|
"checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
|
||||||
"path": "golang.org/x/net/context",
|
"path": "golang.org/x/net/context",
|
||||||
|
|
|
@ -83,6 +83,9 @@ provisioner "file" {
|
||||||
|
|
||||||
* `agent_identity` - The preferred identity from the ssh agent for authentication.
|
* `agent_identity` - The preferred identity from the ssh agent for authentication.
|
||||||
|
|
||||||
|
* `host_key` - The public key from the remote host or the signing CA, used to
|
||||||
|
verify the connection.
|
||||||
|
|
||||||
**Additional arguments only supported by the `winrm` connection type:**
|
**Additional arguments only supported by the `winrm` connection type:**
|
||||||
|
|
||||||
* `https` - Set to `true` to connect using HTTPS instead of HTTP.
|
* `https` - Set to `true` to connect using HTTPS instead of HTTP.
|
||||||
|
@ -100,6 +103,9 @@ The `ssh` connection also supports the following fields to facilitate connnectio
|
||||||
* `bastion_host` - Setting this enables the bastion Host connection. This host
|
* `bastion_host` - Setting this enables the bastion Host connection. This host
|
||||||
will be connected to first, and then the `host` connection will be made from there.
|
will be connected to first, and then the `host` connection will be made from there.
|
||||||
|
|
||||||
|
* `bastion_host_key` - The public key from the remote host or the signing CA,
|
||||||
|
used to verify the host connection.
|
||||||
|
|
||||||
* `bastion_port` - The port to use connect to the bastion host. Defaults to the
|
* `bastion_port` - The port to use connect to the bastion host. Defaults to the
|
||||||
value of the `port` field.
|
value of the `port` field.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue