Skip to content

Commit

Permalink
Added APOptions []int in client.GSSAPIBindRequest(...) and client.Ini…
Browse files Browse the repository at this point in the history
…tSecContext(...), fixes go-ldap#536
  • Loading branch information
cpuschma authored and p0dalirius committed Nov 14, 2024
1 parent 25c2d48 commit 3a20a5c
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 20 deletions.
18 changes: 11 additions & 7 deletions bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ type GSSAPIClient interface {
// reply token is received from the server, passing the reply token
// to InitSecContext via the token parameters.
// See RFC 4752 section 3.1.
InitSecContext(target string, token []byte) (outputToken []byte, needContinue bool, err error)
InitSecContext(target string, token []byte, APOptions []int) (outputToken []byte, needContinue bool, err error)
// NegotiateSaslAuth performs the last step of the Sasl handshake.
// It takes a token, which, when unwrapped, describes the servers supported
// security layers (first octet) and maximum receive buffer (remaining
Expand Down Expand Up @@ -606,14 +606,18 @@ type GSSAPIBindRequest struct {

// GSSAPIBind performs the GSSAPI SASL bind using the provided GSSAPI client.
func (l *Conn) GSSAPIBind(client GSSAPIClient, servicePrincipal, authzid string) error {
return l.GSSAPIBindRequest(client, &GSSAPIBindRequest{
ServicePrincipalName: servicePrincipal,
AuthZID: authzid,
})
return l.GSSAPIBindRequest(
client,
&GSSAPIBindRequest{
ServicePrincipalName: servicePrincipal,
AuthZID: authzid,
},
[]int{},
)
}

// GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client.
func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) error {
func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest, APOptions []int) error {
//nolint:errcheck
defer client.DeleteSecContext()

Expand All @@ -624,7 +628,7 @@ func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) er
for {
if needInit {
// Establish secure context between client and server.
reqToken, needInit, err = client.InitSecContext(req.ServicePrincipalName, recvToken)
reqToken, needInit, err = client.InitSecContext(req.ServicePrincipalName, recvToken, APOptions)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Client interface {
Modify(*ModifyRequest) error
ModifyDN(*ModifyDNRequest) error
ModifyWithResult(*ModifyRequest) (*ModifyResult, error)
Extended(*ExtendedRequest) (*ExtendedResponse, error)

Compare(dn, attribute, value string) (bool, error)
PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)
Expand Down
100 changes: 100 additions & 0 deletions extended.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package ldap

import (
"fmt"
ber "github.com/go-asn1-ber/asn1-ber"
)

// ExtendedRequest represents an extended request to send to the server
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
type ExtendedRequest struct {
// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
// requestName [0] LDAPOID,
// requestValue [1] OCTET STRING OPTIONAL }

Name string
Value *ber.Packet
Controls []Control
}

// NewExtendedRequest returns a new ExtendedRequest. The value can be
// nil depending on the type of request
func NewExtendedRequest(name string, value *ber.Packet) *ExtendedRequest {
return &ExtendedRequest{
Name: name,
Value: value,
}
}

func (er ExtendedRequest) appendTo(envelope *ber.Packet) error {
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Extended Request")
pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, ber.TagEOC, er.Name, "Extended Request Name"))
if er.Value != nil {
pkt.AppendChild(er.Value)
}
envelope.AppendChild(pkt)
if len(er.Controls) > 0 {
envelope.AppendChild(encodeControls(er.Controls))
}
return nil
}

// ExtendedResponse represents the response from the directory server
// after sending an extended request
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
type ExtendedResponse struct {
// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
// COMPONENTS OF LDAPResult,
// responseName [10] LDAPOID OPTIONAL,
// responseValue [11] OCTET STRING OPTIONAL }

Name string
Value *ber.Packet
Controls []Control
}

// Extended performs an extended request. The resulting
// ExtendedResponse may return a value in the form of a *ber.Packet
func (l *Conn) Extended(er *ExtendedRequest) (*ExtendedResponse, error) {
msgCtx, err := l.doRequest(er)
if err != nil {
return nil, err
}
defer l.finishMessage(msgCtx)

packet, err := l.readPacket(msgCtx)
if err != nil {
return nil, err
}
if err = GetLDAPError(packet); err != nil {
return nil, err
}

if len(packet.Children[1].Children) < 4 {
return nil, fmt.Errorf(
"ldap: malformed extended response: expected 4 children, got %d",
len(packet.Children),
)
}

response := &ExtendedResponse{
Name: packet.Children[1].Children[3].Data.String(),
Controls: make([]Control, 0),
}

if len(packet.Children) == 3 {
for _, child := range packet.Children[2].Children {
decodedChild, decodeErr := DecodeControl(child)
if decodeErr != nil {
return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
}
response.Controls = append(response.Controls, decodedChild)
}
}

if len(packet.Children[1].Children) == 5 {
response.Value = packet.Children[1].Children[4]
}

return response, nil
}
41 changes: 41 additions & 0 deletions extended_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ldap

import (
"testing"
)

func TestExtendedRequest_WhoAmI(t *testing.T) {
l, err := DialURL(ldapServer)
if err != nil {
t.Errorf("%s failed: %v", t.Name(), err)
return
}
defer l.Close()

l.Bind("", "") // anonymous
defer l.Unbind()

rfc4532req := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil) // request value is <nil>

var rfc4532resp *ExtendedResponse
if rfc4532resp, err = l.Extended(rfc4532req); err != nil {
t.Errorf("%s failed: %v", t.Name(), err)
return
}
t.Logf("%#v\n", rfc4532resp)
}

func TestExtendedRequest_FastBind(t *testing.T) {
conn, err := DialURL(ldapServer)
if err != nil {
t.Error(err)
}
defer conn.Close()

request := NewExtendedRequest("1.3.6.1.4.1.4203.1.11.3", nil)
_, err = conn.Extended(request)
if err != nil {
t.Errorf("%s failed: %v", t.Name(), err)
return
}
}
56 changes: 53 additions & 3 deletions gssapi/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package gssapi

import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"

"github.com/jcmturner/gokrb5/v8/client"
Expand Down Expand Up @@ -99,7 +103,7 @@ func (client *Client) DeleteSecContext() error {
// InitSecContext initiates the establishment of a security context for
// GSS-API between the client and server.
// See RFC 4752 section 3.1.
func (client *Client) InitSecContext(target string, input []byte) ([]byte, bool, error) {
func (client *Client) InitSecContext(target string, input []byte, APOptions []int) ([]byte, bool, error) {
gssapiFlags := []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf, gssapi.ContextFlagMutual}

switch input {
Expand All @@ -110,7 +114,7 @@ func (client *Client) InitSecContext(target string, input []byte) ([]byte, bool,
}
client.ekey = ekey

token, err := spnego.NewKRB5TokenAPREQ(client.Client, tkt, ekey, gssapiFlags, []int{})
token, err := spnego.NewKRB5TokenAPREQ(client.Client, tkt, ekey, gssapiFlags, APOptions)
if err != nil {
return nil, false, err
}
Expand Down Expand Up @@ -160,7 +164,7 @@ func (client *Client) InitSecContext(target string, input []byte) ([]byte, bool,
// See RFC 4752 section 3.1.
func (client *Client) NegotiateSaslAuth(input []byte, authzid string) ([]byte, error) {
token := &gssapi.WrapToken{}
err := token.Unmarshal(input, true)
err := UnmarshalWrapToken(token, input, true)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -212,3 +216,49 @@ func (client *Client) NegotiateSaslAuth(input []byte, authzid string) ([]byte, e

return output, nil
}

func getGssWrapTokenId() *[2]byte {
return &[2]byte{0x05, 0x04}
}

func UnmarshalWrapToken(wt *gssapi.WrapToken, b []byte, expectFromAcceptor bool) error {
// Check if we can read a whole header
if len(b) < 16 {
return errors.New("bytes shorter than header length")
}
// Is the Token ID correct?
if !bytes.Equal(getGssWrapTokenId()[:], b[0:2]) {
return fmt.Errorf("wrong Token ID. Expected %s, was %s",
hex.EncodeToString(getGssWrapTokenId()[:]),
hex.EncodeToString(b[0:2]))
}
// Check the acceptor flag
flags := b[2]
isFromAcceptor := flags&0x01 == 1
if isFromAcceptor && !expectFromAcceptor {
return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
}
if !isFromAcceptor && expectFromAcceptor {
return errors.New("expected acceptor flag is not set: expecting a token from the acceptor, not the initiator")
}
// Check the filler byte
if b[3] != gssapi.FillerByte {
return fmt.Errorf("unexpected filler byte: expecting 0xFF, was %s ", hex.EncodeToString(b[3:4]))
}
checksumL := binary.BigEndian.Uint16(b[4:6])
// Sanity check on the checksum length
if int(checksumL) > len(b)-gssapi.HdrLen {
return fmt.Errorf("inconsistent checksum length: %d bytes to parse, checksum length is %d", len(b), checksumL)
}

payloadStart := 16 + checksumL

wt.Flags = flags
wt.EC = checksumL
wt.RRC = binary.BigEndian.Uint16(b[6:8])
wt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
wt.CheckSum = b[16:payloadStart]
wt.Payload = b[payloadStart:]

return nil
}
18 changes: 11 additions & 7 deletions v3/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ type GSSAPIClient interface {
// reply token is received from the server, passing the reply token
// to InitSecContext via the token parameters.
// See RFC 4752 section 3.1.
InitSecContext(target string, token []byte) (outputToken []byte, needContinue bool, err error)
InitSecContext(target string, token []byte, APOptions []int) (outputToken []byte, needContinue bool, err error)
// NegotiateSaslAuth performs the last step of the Sasl handshake.
// It takes a token, which, when unwrapped, describes the servers supported
// security layers (first octet) and maximum receive buffer (remaining
Expand Down Expand Up @@ -606,14 +606,18 @@ type GSSAPIBindRequest struct {

// GSSAPIBind performs the GSSAPI SASL bind using the provided GSSAPI client.
func (l *Conn) GSSAPIBind(client GSSAPIClient, servicePrincipal, authzid string) error {
return l.GSSAPIBindRequest(client, &GSSAPIBindRequest{
ServicePrincipalName: servicePrincipal,
AuthZID: authzid,
})
return l.GSSAPIBindRequest(
client,
&GSSAPIBindRequest{
ServicePrincipalName: servicePrincipal,
AuthZID: authzid,
},
[]int{},
)
}

// GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client.
func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) error {
func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest, APOptions []int) error {
//nolint:errcheck
defer client.DeleteSecContext()

Expand All @@ -624,7 +628,7 @@ func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) er
for {
if needInit {
// Establish secure context between client and server.
reqToken, needInit, err = client.InitSecContext(req.ServicePrincipalName, recvToken)
reqToken, needInit, err = client.InitSecContext(req.ServicePrincipalName, recvToken, APOptions)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions v3/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Client interface {
Modify(*ModifyRequest) error
ModifyDN(*ModifyDNRequest) error
ModifyWithResult(*ModifyRequest) (*ModifyResult, error)
Extended(*ExtendedRequest) (*ExtendedResponse, error)

Compare(dn, attribute, value string) (bool, error)
PasswordModify(*PasswordModifyRequest) (*PasswordModifyResult, error)
Expand Down
Loading

0 comments on commit 3a20a5c

Please sign in to comment.