Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Negotiate improvements #4

Merged
merged 6 commits into from
Jun 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 128 additions & 24 deletions negotiate/negotiate.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func init() {
}
}

func acquireCredentials(creduse uint32, ai *sspi.SEC_WINNT_AUTH_IDENTITY) (*sspi.Credentials, error) {
c, err := sspi.AcquireCredentials(sspi.NEGOSSP_NAME, creduse, (*byte)(unsafe.Pointer(ai)))
func acquireCredentials(principalName string, creduse uint32, ai *sspi.SEC_WINNT_AUTH_IDENTITY) (*sspi.Credentials, error) {
c, err := sspi.AcquireCredentials(principalName, sspi.NEGOSSP_NAME, creduse, (*byte)(unsafe.Pointer(ai)))
if err != nil {
return nil, err
}
Expand All @@ -43,7 +43,7 @@ func acquireCredentials(creduse uint32, ai *sspi.SEC_WINNT_AUTH_IDENTITY) (*sspi
// itself to the server. It will also be used by the server
// to impersonate the user.
func AcquireCurrentUserCredentials() (*sspi.Credentials, error) {
return acquireCredentials(sspi.SECPKG_CRED_OUTBOUND, nil)
return acquireCredentials("", sspi.SECPKG_CRED_OUTBOUND, nil)
}

// TODO: see if I can share this common ntlm and negotiate code
Expand Down Expand Up @@ -80,13 +80,20 @@ func AcquireUserCredentials(domain, username, password string) (*sspi.Credential
PasswordLength: uint32(len(p) - 1), // do not count terminating 0
Flags: sspi.SEC_WINNT_AUTH_IDENTITY_UNICODE,
}
return acquireCredentials(sspi.SECPKG_CRED_OUTBOUND, &ai)
return acquireCredentials("", sspi.SECPKG_CRED_OUTBOUND, &ai)
}

// AcquireServerCredentials acquires server credentials that will
// be used to authenticate client.
func AcquireServerCredentials() (*sspi.Credentials, error) {
return acquireCredentials(sspi.SECPKG_CRED_INBOUND, nil)
// be used to authenticate clients.
// The principalName parameter is passed to the underlying call to
// the winapi AcquireCredentialsHandle function (and specifies the
// name of the principal whose credentials the underlying handle
// will reference).
// As a special case, using an empty string for the principal name
// will require the credential of the user under whose security context
// the current process is running.
func AcquireServerCredentials(principalName string) (*sspi.Credentials, error) {
return acquireCredentials(principalName, sspi.SECPKG_CRED_INBOUND, nil)
}

func updateContext(c *sspi.Context, dst, src []byte, targetName *uint16) (authCompleted bool, n int, err error) {
Expand Down Expand Up @@ -142,6 +149,49 @@ func makeSignature(c *sspi.Context, msg []byte, qop, seqno uint32) ([]byte, erro
return b[1].Bytes(), nil
}

func encryptMessage(c *sspi.Context, msg []byte, qop, seqno uint32) ([]byte, error) {
_ /*maxToken*/, maxSignature, cBlockSize, cSecurityTrailer, err := c.Sizes()
if err != nil {
return nil, err
}

if maxSignature == 0 {
return nil, errors.New("integrity services are not requested or unavailable")
}

var b [3]sspi.SecBuffer
b[0].Set(sspi.SECBUFFER_TOKEN, make([]byte, cSecurityTrailer))
b[1].Set(sspi.SECBUFFER_DATA, msg)
b[2].Set(sspi.SECBUFFER_PADDING, make([]byte, cBlockSize))

ret := sspi.EncryptMessage(c.Handle, qop, sspi.NewSecBufferDesc(b[:]), seqno)
if ret != sspi.SEC_E_OK {
return nil, ret
}

r0, r1, r2 := b[0].Bytes(), b[1].Bytes(), b[2].Bytes()
res := make([]byte, 0, len(r0)+len(r1)+len(r2))
res = append(res, r0...)
res = append(res, r1...)
res = append(res, r2...)

return res, nil
}

func decryptMessage(c *sspi.Context, msg []byte, seqno uint32) (uint32, []byte, error) {
var b [2]sspi.SecBuffer
b[0].Set(sspi.SECBUFFER_STREAM, msg)
b[1].Set(sspi.SECBUFFER_DATA, []byte{})

var qop uint32
ret := sspi.DecryptMessage(c.Handle, sspi.NewSecBufferDesc(b[:]), seqno, &qop)
if ret != sspi.SEC_E_OK {
return qop, nil, ret
}

return qop, b[1].Bytes(), nil
}

func verifySignature(c *sspi.Context, msg, token []byte, seqno uint32) (uint32, error) {
var b [2]sspi.SecBuffer
b[0].Set(sspi.SECBUFFER_DATA, msg)
Expand All @@ -163,13 +213,25 @@ type ClientContext struct {
targetName *uint16
}

// NewClientContext creates new client context. It uses client
// NewClientContext creates a new client context. It uses client
// credentials cred generated by AcquireCurrentUserCredentials or
// AcquireUserCredentials and SPN to start client Negotiate
// AcquireUserCredentials and SPN to start a client Negotiate
// negotiation sequence. targetName is the service principal name
// (SPN) or the security context of the destination server.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do not mention new "flags" parameter in the documentation. Don't you want to say something about it?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You did not address my comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flags parameter is documented in AcquireUserCredentialsWithFlags, did you mean something else ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My fault, I have no idea why I asked you to document "flag" parameter here. Forget my comment.

// NewClientContext returns new token to be sent to the server.
// NewClientContext returns a new token to be sent to the server.
func NewClientContext(cred *sspi.Credentials, targetName string) (cc *ClientContext, outputToken []byte, err error) {
return NewClientContextWithFlags(cred, targetName, sspi.ISC_REQ_CONNECTION)
}

// NewClientContextWithFlags creates a new client context. It uses client
// credentials cred generated by AcquireCurrentUserCredentials or
// AcquireUserCredentials and SPN to start a client Negotiate
// negotiation sequence. targetName is the service principal name
// (SPN) or the security context of the destination server.
// The flags parameter is used to indicate requests for the context
// (for example sspi.ISC_REQ_CONFIDENTIALITY|sspi.ISC_REQ_REPLAY_DETECT)
// NewClientContextWithFlags returns a new token to be sent to the server.
func NewClientContextWithFlags(cred *sspi.Credentials, targetName string, flags uint32) (cc *ClientContext, outputToken []byte, err error) {
var tname *uint16
if len(targetName) > 0 {
p, err2 := syscall.UTF16FromString(targetName)
Expand All @@ -181,7 +243,8 @@ func NewClientContext(cred *sspi.Credentials, targetName string) (cc *ClientCont
}
}
otoken := make([]byte, PackageInfo.MaxToken)
c := sspi.NewClientContext(cred, sspi.ISC_REQ_CONNECTION)
c := sspi.NewClientContext(cred, flags)

authCompleted, n, err2 := updateContext(c, otoken, nil, tname)
if err2 != nil {
return nil, nil, err2
Expand Down Expand Up @@ -248,36 +311,42 @@ func (c *ClientContext) VerifySignature(msg, token []byte, seqno uint32) (uint32
return verifySignature(c.sctxt, msg, token, seqno)
}

// EncryptMessage uses the established client context to encrypt a message
// using the provided quality of protection flags and sequence number.
// It returns the signature token in addition to any error.
// IMPORTANT: the input msg parameter is updated in place by the low-level windows api
// so must be copied if the initial content should not be modified.
func (c *ClientContext) EncryptMessage(msg []byte, qop, seqno uint32) ([]byte, error) {
return encryptMessage(c.sctxt, msg, qop, seqno)
}

// DecryptMessage uses the established client context to decrypt a message
// using the provided sequence number.
// It returns the quality of protection flag and the decrypted message in addition to any error.
func (c *ClientContext) DecryptMessage(msg []byte, seqno uint32) (uint32, []byte, error) {
return decryptMessage(c.sctxt, msg, seqno)
}

// ServerContext is used by the server to manage all steps of Negotiate
// negotiation. Once authentication is completed the context can be
// used to impersonate client.
type ServerContext struct {
sctxt *sspi.Context
}

// TODO: I suspect NewServerContext might be the call to complete auth sometimes (see http://blogs.technet.com/b/tristank/archive/2006/08/02/negotiate-this.aspx) - we might need to redesign this call to return authCompleted or similar

// NewServerContext creates new server context. It uses server
// credentials created by AcquireServerCredentials and token from
// the client to start server Negotiate negotiation sequence.
// It also returns new token to be sent to the client.
func NewServerContext(cred *sspi.Credentials, token []byte) (sc *ServerContext, outputToken []byte, err error) {
func NewServerContext(cred *sspi.Credentials, token []byte) (sc *ServerContext, authDone bool, outputToken []byte, err error) {
otoken := make([]byte, PackageInfo.MaxToken)
c := sspi.NewServerContext(cred, sspi.ASC_REQ_CONNECTION)
authDone, n, err2 := updateContext(c, otoken, token, nil)
if err2 != nil {
return nil, nil, err2
}
if authDone {
c.Release()
return nil, nil, errors.New("negotiate authentication should not be completed yet")
}
if n == 0 {
c.Release()
return nil, nil, errors.New("negotiate token should not be empty")
return nil, false, nil, err2
}
otoken = otoken[:n]
return &ServerContext{sctxt: c}, otoken, nil
return &ServerContext{sctxt: c}, authDone, otoken, nil
}

// Release free up resources associated with server context c.
Expand Down Expand Up @@ -307,6 +376,25 @@ func (c *ServerContext) Update(token []byte) (authCompleted bool, outputToken []
return authDone, otoken, nil
}

const _SECPKG_ATTR_NATIVE_NAMES = 13

type _SecPkgContext_NativeNames struct {
ClientName *uint16
ServerName *uint16
}

// GetUsername returns the username corresponding to the authenticated client
func (c *ServerContext) GetUsername() (string, error) {
var ns _SecPkgContext_NativeNames
ret := sspi.QueryContextAttributes(c.sctxt.Handle, _SECPKG_ATTR_NATIVE_NAMES, (*byte)(unsafe.Pointer(&ns)))
if ret != sspi.SEC_E_OK {
return "", ret
}
sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ns.ServerName)))
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ns.ClientName)))
return syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(ns.ClientName))[:]), nil
}

// ImpersonateUser changes current OS thread user. New user is
// the user as specified by client credentials.
func (c *ServerContext) ImpersonateUser() error {
Expand Down Expand Up @@ -341,3 +429,19 @@ func (c *ServerContext) MakeSignature(msg []byte, qop, seqno uint32) ([]byte, er
func (c *ServerContext) VerifySignature(msg, token []byte, seqno uint32) (uint32, error) {
return verifySignature(c.sctxt, msg, token, seqno)
}

// EncryptMessage uses the established server context to encrypt a message
// using the provided quality of protection flags and sequence number.
// It returns the signature token in addition to any error.
// IMPORTANT: the input msg parameter is updated in place by the low-level windows api
// so must be copied if the initial content should not be modified.
func (c *ServerContext) EncryptMessage(msg []byte, qop, seqno uint32) ([]byte, error) {
return encryptMessage(c.sctxt, msg, qop, seqno)
}

// DecryptMessage uses the established server context to decrypt a message
// using the provided sequence number.
// It returns the quality of protection flag and the decrypted message in addition to any error.
func (c *ServerContext) DecryptMessage(msg []byte, seqno uint32) (uint32, []byte, error) {
return decryptMessage(c.sctxt, msg, seqno)
}
46 changes: 36 additions & 10 deletions negotiate/negotiate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package negotiate_test

import (
"bytes"
"crypto/rand"
"flag"
"os"
Expand Down Expand Up @@ -52,7 +53,7 @@ func testNegotiate(t *testing.T, clientCred *sspi.Credentials, SPN string) {
t.Logf("testing with SPN=%s", SPN)
}

serverCred, err := negotiate.AcquireServerCredentials()
serverCred, err := negotiate.AcquireServerCredentials("")
if err != nil {
t.Fatal(err)
}
Expand All @@ -71,15 +72,15 @@ func testNegotiate(t *testing.T, clientCred *sspi.Credentials, SPN string) {

testContextExpiry(t, "client security context", client)

server, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
server, serverDone, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
if err != nil {
t.Fatal(err)
}
defer server.Release()

testContextExpiry(t, "server security context", server)

var clientDone, serverDone bool
var clientDone bool
for {
if len(toClientToken) == 0 {
break
Expand Down Expand Up @@ -145,7 +146,7 @@ func TestNegotiateFailure(t *testing.T) {
}
defer clientCred.Release()

serverCred, err := negotiate.AcquireServerCredentials()
serverCred, err := negotiate.AcquireServerCredentials("")
if err != nil {
t.Fatal(err)
}
Expand All @@ -162,14 +163,14 @@ func TestNegotiateFailure(t *testing.T) {
}
t.Logf("sent %d bytes to server", len(toServerToken))

server, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
server, serverDone, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
if err != nil {
t.Fatal(err)
}
defer server.Release()

for {
var clientDone, serverDone bool
var clientDone bool
if len(toClientToken) == 0 {
t.Fatal("token for client cannot be empty")
}
Expand Down Expand Up @@ -219,14 +220,14 @@ func TestAcquireUserCredentials(t *testing.T) {
testNegotiate(t, cred, "")
}

func TestSignature(t *testing.T) {
func TestSignatureEncryption(t *testing.T) {
clientCred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer clientCred.Release()

serverCred, err := negotiate.AcquireServerCredentials()
serverCred, err := negotiate.AcquireServerCredentials("")
if err != nil {
t.Fatal(err)
}
Expand All @@ -242,13 +243,13 @@ func TestSignature(t *testing.T) {
t.Fatal("token for server cannot be empty")
}

server, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
server, serverDone, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
if err != nil {
t.Fatal(err)
}
defer server.Release()

var clientDone, serverDone bool
var clientDone bool
for {
if len(toClientToken) == 0 {
break
Expand Down Expand Up @@ -291,6 +292,25 @@ func TestSignature(t *testing.T) {
}
t.Logf("server verified client signature")

var clientQop uint32
clientCrypt, err := client.EncryptMessage(copyArray(clientMsg), clientQop, 0)
if err != nil {
t.Fatal(err)
}
t.Logf("clientMsg=%v,clientCrypt=%v", clientMsg, clientCrypt)

qop, m2, err := server.DecryptMessage(clientCrypt, 0)
if err != nil {
t.Fatal(err)
}
if qop != clientQop {
t.Fatalf("Wrong value %d for qop", qop)
}
if !bytes.Equal(clientMsg, m2) {
t.Fatalf("Wrong value %v for message decrypted by server (expected %v)", m2, clientMsg)
}
t.Logf("server decrypted client message")

serverMsg := make([]byte, 10)
_, err = rand.Read(serverMsg)
if err != nil {
Expand All @@ -310,3 +330,9 @@ func TestSignature(t *testing.T) {
}
t.Logf("client verified server signature")
}

func copyArray(a []byte) []byte {
b := make([]byte, len(a))
copy(b, a)
return b
}
2 changes: 1 addition & 1 deletion ntlm/ntlm.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func init() {
}

func acquireCredentials(creduse uint32, ai *sspi.SEC_WINNT_AUTH_IDENTITY) (*sspi.Credentials, error) {
c, err := sspi.AcquireCredentials(sspi.NTLMSP_NAME, creduse, (*byte)(unsafe.Pointer(ai)))
c, err := sspi.AcquireCredentials("", sspi.NTLMSP_NAME, creduse, (*byte)(unsafe.Pointer(ai)))
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion schannel/creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func acquireCredentials(creduse uint32) (*sspi.Credentials, error) {
// TODO: allow for EnabledProtocols
// TODO: allow for MinimumCipherStrength / MaximumCipherStrength
}
c, err := sspi.AcquireCredentials(sspi.UNISP_NAME, creduse, (*byte)(unsafe.Pointer(sc)))
c, err := sspi.AcquireCredentials("", sspi.UNISP_NAME, creduse, (*byte)(unsafe.Pointer(sc)))
if err != nil {
return nil, err
}
Expand Down
Loading