go.crypto/ssh: implement keyboard-interactive auth (RFC 4256), both
on client and server-side. R=dave, agl CC=gobot, golang-dev https://golang.org/cl/9853050
This commit is contained in:
committed by
Adam Langley
parent
36bf31eb71
commit
c7df565dd4
@@ -375,3 +375,120 @@ func (kr *agentKeyring) Sign(i int, rand io.Reader, data []byte) (sig []byte, er
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// ClientKeyboardInteractive should prompt the user for the given
|
||||
// questions.
|
||||
type ClientKeyboardInteractive interface {
|
||||
// Challenge should print the questions, optionally disabling
|
||||
// echoing (eg. for passwords), and return all the answers.
|
||||
// Challenge may be called multiple times in a single
|
||||
// session. After successful authentication, the server may
|
||||
// send a challenge with no questions, for which the user and
|
||||
// instruction messages should be printed. RFC 4256 section
|
||||
// 3.3 details how the UI should behave for both CLI and
|
||||
// GUI environments.
|
||||
Challenge(user, instruction string, questions []string, echos []bool) ([]string, error)
|
||||
}
|
||||
|
||||
// ClientAuthKeyboardInteractive returns a ClientAuth using a
|
||||
// prompt/response sequence controlled by the server.
|
||||
func ClientAuthKeyboardInteractive(impl ClientKeyboardInteractive) ClientAuth {
|
||||
return &keyboardInteractiveAuth{impl}
|
||||
}
|
||||
|
||||
type keyboardInteractiveAuth struct {
|
||||
ClientKeyboardInteractive
|
||||
}
|
||||
|
||||
func (c *keyboardInteractiveAuth) method() string {
|
||||
return "keyboard-interactive"
|
||||
}
|
||||
|
||||
func (c *keyboardInteractiveAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
|
||||
type initiateMsg struct {
|
||||
User string
|
||||
Service string
|
||||
Method string
|
||||
Language string
|
||||
Submethods string
|
||||
}
|
||||
|
||||
if err := t.writePacket(marshal(msgUserAuthRequest, initiateMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "keyboard-interactive",
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
packet, err := t.readPacket()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// like handleAuthResponse, but with less options.
|
||||
switch packet[0] {
|
||||
case msgUserAuthInfoRequest:
|
||||
// OK
|
||||
case msgUserAuthFailure:
|
||||
var msg userAuthFailureMsg
|
||||
if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return false, msg.Methods, nil
|
||||
case msgUserAuthSuccess:
|
||||
return true, nil, nil
|
||||
default:
|
||||
return false, nil, UnexpectedMessageError{msgUserAuthInfoRequest, packet[0]}
|
||||
}
|
||||
|
||||
var msg userAuthInfoRequestMsg
|
||||
if err := unmarshal(&msg, packet, packet[0]); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// Manually unpack the prompt/echo pairs.
|
||||
rest := msg.Prompts
|
||||
var prompts []string
|
||||
var echos []bool
|
||||
for i := 0; i < int(msg.NumPrompts); i++ {
|
||||
prompt, r, ok := parseString(rest)
|
||||
if !ok || len(r) == 0 {
|
||||
return false, nil, errors.New("ssh: prompt format error")
|
||||
}
|
||||
prompts = append(prompts, string(prompt))
|
||||
echos = append(echos, r[0] != 0)
|
||||
rest = r[1:]
|
||||
}
|
||||
|
||||
if len(rest) != 0 {
|
||||
return false, nil, fmt.Errorf("ssh: junk following message %q", rest)
|
||||
}
|
||||
|
||||
answers, err := c.Challenge(msg.User, msg.Instruction, prompts, echos)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if len(answers) != len(prompts) {
|
||||
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
||||
}
|
||||
responseLength := 1 + 4
|
||||
for _, a := range answers {
|
||||
responseLength += stringLength(len(a))
|
||||
}
|
||||
serialized := make([]byte, responseLength)
|
||||
p := serialized
|
||||
p[0] = msgUserAuthInfoResponse
|
||||
p = p[1:]
|
||||
p = marshalUint32(p, uint32(len(answers)))
|
||||
for _, a := range answers {
|
||||
p = marshalString(p, []byte(a))
|
||||
}
|
||||
|
||||
if err := t.writePacket(serialized); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user