diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7448756 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..848b6b5 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +This repository holds Go packages for accessing Security Support Provider Interface on Windows. diff --git a/ntlm/http_test.go b/ntlm/http_test.go new file mode 100644 index 0000000..c27e54a --- /dev/null +++ b/ntlm/http_test.go @@ -0,0 +1,183 @@ +// 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 windows + +package ntlm_test + +import ( + "encoding/base64" + "flag" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "sspi/ntlm" +) + +var ( + testURL = flag.String("url", "", "server URL for TestNTLMHTTPClient") +) + +// TODO: implement Negotiate authentication. Probably in a different package. +// see http://blogs.technet.com/b/tristank/archive/2006/08/02/negotiate-this.aspx +// for how to disinguish between NTLM and Kerberos during Negotiate + +func newRequest() (*http.Request, error) { + req, err := http.NewRequest("GET", *testURL, nil) + if err != nil { + return nil, err + } + return req, nil +} + +func get(req *http.Request) (*http.Response, string, error) { + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, "", err + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, "", err + } + return res, string(body), nil +} + +func canDoNTLM() error { + req, err := newRequest() + if err != nil { + return err + } + res, _, err := get(req) + if err != nil { + return err + } + if res.StatusCode != http.StatusUnauthorized { + return fmt.Errorf("Unauthorized expected, but got %v", res.StatusCode) + } + authHeaders, found := res.Header["Www-Authenticate"] + if !found { + return fmt.Errorf("Www-Authenticate not found") + } + for _, h := range authHeaders { + if h == "NTLM" { + return nil + } + } + return fmt.Errorf("Www-Authenticate header does not contain NTLM, but has %v", authHeaders) +} + +func doNTLMNegotiate(negotiate []byte) ([]byte, error) { + req, err := newRequest() + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(negotiate)) + res, _, err := get(req) + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusUnauthorized { + return nil, fmt.Errorf("Unauthorized expected, but got %v", res.StatusCode) + } + authHeaders, found := res.Header["Www-Authenticate"] + if !found { + return nil, fmt.Errorf("Www-Authenticate not found") + } + if len(authHeaders) != 1 { + return nil, fmt.Errorf("Only one Www-Authenticate header expected, but %d found: %v", len(authHeaders), authHeaders) + } + if len(authHeaders[0]) < 6 { + return nil, fmt.Errorf("Www-Authenticate header is to short: %q", authHeaders[0]) + } + if !strings.HasPrefix(authHeaders[0], "NTLM ") { + return nil, fmt.Errorf("Www-Authenticate header is suppose to starts with \"NTLM \", but is %q", authHeaders[0]) + } + authenticate, err := base64.StdEncoding.DecodeString(authHeaders[0][5:]) + if err != nil { + return nil, err + } + return authenticate, nil +} + +func doNTLMAuthenticate(authenticate []byte) (string, error) { + req, err := newRequest() + if err != nil { + return "", err + } + req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(authenticate)) + res, body, err := get(req) + if err != nil { + return "", err + } + if res.StatusCode != http.StatusOK { + return "", fmt.Errorf("OK expected, but got %v", res.StatusCode) + } + return body, nil +} + +func TestNTLMHTTPClient(t *testing.T) { + // TODO: combine client and server tests so we don't need external server + if len(*testURL) == 0 { + t.Skip("Skipping due to empty \"url\" parameter") + } + + cred, err := ntlm.AcquireCurrentUserCredentials() + if err != nil { + t.Fatal(err) + } + defer cred.Release() + + secctx, negotiate, err := ntlm.NewClientContext(cred) + if err != nil { + t.Fatal(err) + } + defer secctx.Release() + + err = canDoNTLM() + if err != nil { + t.Fatal(err) + } + challenge, err := doNTLMNegotiate(negotiate) + if err != nil { + t.Fatal(err) + } + authenticate, err := secctx.Update(challenge) + if err != nil { + t.Fatal(err) + } + _, err = doNTLMAuthenticate(authenticate) + if err != nil { + t.Fatal(err) + } +} + +// TODO: See http://www.innovation.ch/personal/ronald/ntlm.html#connections about needed to keep connection alive during authentication. + +func TestNTLMHTTPServer(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // TODO: implement NTLM authentication here + w.Write([]byte("hello")) + })) + defer ts.Close() + + go func() { + res, err := http.Get(ts.URL) + if err != nil { + t.Fatal(err) + } + got, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + if string(got) != "hello" { + t.Errorf("got %q, want hello", string(got)) + } + }() +} diff --git a/ntlm/ntlm.go b/ntlm/ntlm.go new file mode 100644 index 0000000..aa17cc5 --- /dev/null +++ b/ntlm/ntlm.go @@ -0,0 +1,228 @@ +// 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 windows + +// Package ntlm provides access to the Microsoft NTLM SSP Package. +// +package ntlm + +import ( + "errors" + "syscall" + "time" + "unsafe" + + "sspi" +) + +// PackageInfo contains NTLM SSP package description. +var PackageInfo *sspi.PackageInfo + +func init() { + var err error + PackageInfo, err = sspi.QueryPackageInfo(sspi.NTLMSP_NAME) + if err != nil { + panic("failed to fetch NTLM package info: " + err.Error()) + } +} + +const _SEC_WINNT_AUTH_IDENTITY_UNICODE = 0x2 + +type _SEC_WINNT_AUTH_IDENTITY struct { + User *uint16 + UserLength uint32 + Domain *uint16 + DomainLength uint32 + Password *uint16 + PasswordLength uint32 + Flags uint32 +} + +func acquireCredentials(creduse uint32, ai *_SEC_WINNT_AUTH_IDENTITY) (*sspi.Credentials, error) { + c, err := sspi.AcquireCredentials(sspi.NTLMSP_NAME, creduse, (*byte)(unsafe.Pointer(ai))) + if err != nil { + return nil, err + } + return c, nil +} + +// AcquireCurrentUserCredentials acquires credentials of currently +// logged on user. These will be used by the client to authenticate +// 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) +} + +// AcquireUserCredentials acquires credentials of user described by +// domain, username and password. These will be used by the client to +// authenticate itself to the server. It will also be used by the +// server to impersonate the user. +func AcquireUserCredentials(domain, username, password string) (*sspi.Credentials, error) { + if len(domain) == 0 { + return nil, errors.New("domain parameter cannot be empty") + } + if len(username) == 0 { + return nil, errors.New("username parameter cannot be empty") + } + d, err := syscall.UTF16FromString(domain) + if err != nil { + return nil, err + } + u, err := syscall.UTF16FromString(username) + if err != nil { + return nil, err + } + var p []uint16 + var plen uint32 + if len(password) > 0 { + p, err = syscall.UTF16FromString(password) + if err != nil { + return nil, err + } + plen = uint32(len(p) - 1) // do not count terminating 0 + } + ai := _SEC_WINNT_AUTH_IDENTITY{ + User: &u[0], + UserLength: uint32(len(u) - 1), // do not count terminating 0 + Domain: &d[0], + DomainLength: uint32(len(d) - 1), // do not count terminating 0 + Password: &p[0], + PasswordLength: plen, + Flags: _SEC_WINNT_AUTH_IDENTITY_UNICODE, + } + 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) +} + +// ClientContext is used by the client to manage all steps of NTLM negotiation. +type ClientContext struct { + sctxt *sspi.Context +} + +// NewClientContext creates new client context. It uses client +// credentials cred generated by AcquireCurrentUserCredentials or +// AcquireUserCredentials and, if successful, outputs negotiate +// message. Negotiate message needs to be sent to the server to +// start NTLM negotiation sequence. +func NewClientContext(cred *sspi.Credentials) (*ClientContext, []byte, error) { + negotiate := make([]byte, PackageInfo.MaxToken) + c, authCompleted, n, err := sspi.NewClientContext(cred, sspi.ISC_REQ_CONNECTION, negotiate) + if err != nil { + return nil, nil, err + } + if authCompleted { + c.Release() + return nil, nil, errors.New("ntlm authentication should not be completed yet") + } + if n == 0 { + c.Release() + return nil, nil, errors.New("ntlm token should not be empty") + } + negotiate = negotiate[:n] + return &ClientContext{sctxt: c}, negotiate, nil +} + +// Release free up resources associated with client context c. +func (c *ClientContext) Release() error { + return c.sctxt.Release() +} + +// Expiry returns c expiry time. +func (c *ClientContext) Expiry() time.Time { + return c.sctxt.Expiry() +} + +// Update completes client part of NTLM negotiation c. It uses +// challenge message received from the server, and generates +// authenticate message to be returned to the server. +func (c *ClientContext) Update(challenge []byte) ([]byte, error) { + authenticate := make([]byte, PackageInfo.MaxToken) + authCompleted, n, err := c.sctxt.Update(authenticate, challenge) + if err != nil { + return nil, err + } + if !authCompleted { + return nil, errors.New("ntlm authentication should be completed now") + } + if n == 0 { + return nil, errors.New("ntlm token should not be empty") + } + authenticate = authenticate[:n] + return authenticate, nil +} + +// ServerContext is used by the server to manage all steps of NTLM +// negotiation. Once authentication is completed the context can be +// used to impersonate client. +type ServerContext struct { + sctxt *sspi.Context +} + +// NewServerContext creates new server context. It uses server +// credentials created by AcquireServerCredentials and client +// negotiate message and, if successful, outputs challenge message. +// Challenge message needs to be sent to the client to continue +// NTLM negotiation sequence. +func NewServerContext(cred *sspi.Credentials, negotiate []byte) (*ServerContext, []byte, error) { + challenge := make([]byte, PackageInfo.MaxToken) + c, authCompleted, n, err := sspi.NewServerContext(cred, sspi.ISC_REQ_CONNECTION, challenge, negotiate) + if err != nil { + return nil, nil, err + } + if authCompleted { + c.Release() + return nil, nil, errors.New("ntlm authentication should not be completed yet") + } + if n == 0 { + c.Release() + return nil, nil, errors.New("ntlm token should not be empty") + } + challenge = challenge[:n] + return &ServerContext{sctxt: c}, challenge, nil +} + +// Release free up resources associated with server context c. +func (c *ServerContext) Release() error { + return c.sctxt.Release() +} + +// Expiry returns c expiry time. +func (c *ServerContext) Expiry() time.Time { + return c.sctxt.Expiry() +} + +// Update completes server part of NTLM negotiation c. It uses +// authenticate message received from the client. +func (c *ServerContext) Update(authenticate []byte) error { + authCompleted, n, err := c.sctxt.Update(nil, authenticate) + if err != nil { + return err + } + if !authCompleted { + return errors.New("ntlm authentication should be completed now") + } + if n != 0 { + return errors.New("ntlm token should be empty now") + } + return nil +} + +// ImpersonateUser changes current OS thread user. New user is +// the user as specified by client credentials. +func (c *ServerContext) ImpersonateUser() error { + return c.sctxt.ImpersonateUser() +} + +// RevertToSelf stops impersonation. It changes current OS thread +// user to what it was before ImpersonateUser was executed. +func (c *ServerContext) RevertToSelf() error { + return c.sctxt.RevertToSelf() +} diff --git a/ntlm/ntlm_test.go b/ntlm/ntlm_test.go new file mode 100644 index 0000000..0d234b2 --- /dev/null +++ b/ntlm/ntlm_test.go @@ -0,0 +1,119 @@ +// 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 windows + +package ntlm_test + +import ( + "flag" + "os/user" + "runtime" + "testing" + "time" + + "sspi" + "sspi/ntlm" +) + +var ( + testDomain = flag.String("domain", "", "domain parameter for TestAcquireUserCredentials") + testUsername = flag.String("username", "", "username parameter for TestAcquireUserCredentials") + testPassword = flag.String("password", "", "password parameter for TestAcquireUserCredentials") +) + +func TestPackageInfo(t *testing.T) { + if ntlm.PackageInfo.Name != "NTLM" { + t.Fatalf(`invalid NTLM package name of %q, "NTLM" is expected.`, ntlm.PackageInfo.Name) + } +} + +func testContextExpiry(t *testing.T, name string, c interface { + Expiry() time.Time +}) { + validFor := c.Expiry().Sub(time.Now()) + if validFor < time.Hour { + t.Errorf("%v exipries in %v, more then 1 hour expected", name, validFor) + } + if validFor > 10*24*time.Hour { + t.Errorf("%v exipries in %v, less then 10 days expected", name, validFor) + } +} + +func testNTLM(t *testing.T, clientCred *sspi.Credentials) { + serverCred, err := ntlm.AcquireServerCredentials() + if err != nil { + t.Fatal(err) + } + defer serverCred.Release() + + client, token1, err := ntlm.NewClientContext(clientCred) + if err != nil { + t.Fatal(err) + } + defer client.Release() + + testContextExpiry(t, "clent security context", client) + + server, token2, err := ntlm.NewServerContext(serverCred, token1) + if err != nil { + t.Fatal(err) + } + defer server.Release() + + testContextExpiry(t, "server security context", server) + + token3, err := client.Update(token2) + if err != nil { + t.Fatal(err) + } + + err = server.Update(token3) + if err != nil { + t.Fatal(err) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err = server.ImpersonateUser() + if err != nil { + t.Fatal(err) + } + defer server.RevertToSelf() + + _, err = user.Current() + if err != nil { + t.Fatal(err) + } +} + +func TestNTLM(t *testing.T) { + cred, err := ntlm.AcquireCurrentUserCredentials() + if err != nil { + t.Fatal(err) + } + defer cred.Release() + + testNTLM(t, cred) +} + +func TestAcquireUserCredentials(t *testing.T) { + if len(*testDomain) == 0 { + t.Skip("Skipping due to empty \"domain\" parameter") + } + if len(*testUsername) == 0 { + t.Skip("Skipping due to empty \"username\" parameter") + } + if len(*testPassword) == 0 { + t.Skip("Skipping due to empty \"password\" parameter") + } + cred, err := ntlm.AcquireUserCredentials(*testDomain, *testUsername, *testPassword) + if err != nil { + t.Fatal(err) + } + defer cred.Release() + + testNTLM(t, cred) +} diff --git a/sspi.go b/sspi.go new file mode 100644 index 0000000..b5540ab --- /dev/null +++ b/sspi.go @@ -0,0 +1,201 @@ +// 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 windows + +package sspi + +import ( + "syscall" + "time" + "unsafe" +) + +// TODO: add documentation + +type PackageInfo struct { + Capabilities uint32 + Version uint16 + RPCID uint16 + MaxToken uint32 + Name string + Comment string +} + +func QueryPackageInfo(pkgname string) (*PackageInfo, error) { + name, err := syscall.UTF16PtrFromString(pkgname) + if err != nil { + return nil, err + } + var pi *SecPkgInfo + ret := QuerySecurityPackageInfo(name, &pi) + if ret != SEC_E_OK { + return nil, ret + } + defer FreeContextBuffer((*byte)(unsafe.Pointer(pi))) + + return &PackageInfo{ + Capabilities: pi.Capabilities, + Version: pi.Version, + RPCID: pi.RPCID, + MaxToken: pi.MaxToken, + Name: syscall.UTF16ToString((*[2 << 12]uint16)(unsafe.Pointer(pi.Name))[:]), + Comment: syscall.UTF16ToString((*[2 << 12]uint16)(unsafe.Pointer(pi.Comment))[:]), + }, nil +} + +type Credentials struct { + Handle CredHandle + expiry syscall.Filetime +} + +func AcquireCredentials(pkgname string, creduse uint32, authdata *byte) (*Credentials, error) { + name, err := syscall.UTF16PtrFromString(pkgname) + if err != nil { + return nil, err + } + var c Credentials + ret := AcquireCredentialsHandle(nil, name, creduse, nil, authdata, 0, 0, &c.Handle, &c.expiry) + if ret != SEC_E_OK { + return nil, ret + } + return &c, nil +} + +func (c *Credentials) Release() error { + ret := FreeCredentialsHandle(&c.Handle) + if ret != SEC_E_OK { + return ret + } + return nil +} + +func (c *Credentials) Expiry() time.Time { + return time.Unix(0, c.expiry.Nanoseconds()) +} + +// TODO: add functions to display and manage RequestedFlags and EstablishedFlags fields. +// TODO: maybe get rid of RequestedFlags and EstablishedFlags fields, and replace them with input parameter for New...Context and return value of Update (instead of current bool parameter). + +type updateFunc func(c *Context, h, newh *CtxtHandle, out, in *SecBufferDesc) syscall.Errno + +type Context struct { + Cred *Credentials + Handle *CtxtHandle + handle CtxtHandle + updFn updateFunc + expiry syscall.Filetime + RequestedFlags uint32 + EstablishedFlags uint32 +} + +func newContext(cred *Credentials, flags uint32, updFn updateFunc, dst, src []byte) (nc *Context, authCompleted bool, n int, err error) { + c := &Context{ + Cred: cred, + updFn: updFn, + RequestedFlags: flags, + } + authCompleted, n, err = c.Update(dst, src) + if err != nil { + return nil, authCompleted, 0, err + } + return c, authCompleted, n, nil +} + +func NewClientContext(cred *Credentials, flags uint32, dst []byte) (c *Context, authCompleted bool, n int, err error) { + return newContext(cred, flags, initialize, dst, nil) +} + +func NewServerContext(cred *Credentials, flags uint32, dst, src []byte) (c *Context, authCompleted bool, n int, err error) { + return newContext(cred, flags, accept, dst, src) +} + +func initialize(c *Context, h, newh *CtxtHandle, out, in *SecBufferDesc) syscall.Errno { + return InitializeSecurityContext(&c.Cred.Handle, h, nil, c.RequestedFlags, + 0, SECURITY_NATIVE_DREP, in, 0, newh, out, &c.EstablishedFlags, &c.expiry) +} + +func accept(c *Context, h, newh *CtxtHandle, out, in *SecBufferDesc) syscall.Errno { + return AcceptSecurityContext(&c.Cred.Handle, h, in, c.RequestedFlags, + SECURITY_NATIVE_DREP, newh, out, &c.EstablishedFlags, &c.expiry) +} + +func (c *Context) Update(dst, src []byte) (authCompleted bool, n int, err error) { + // TODO: some of this buffer setup could be done once (when creating Context) and then reused here. + inBuf := &SecBuffer{ + BufferType: SECBUFFER_TOKEN, + } + if len(src) > 0 { + inBuf.BufferSize = uint32(len(src)) + inBuf.Buffer = &src[0] + } + inBufs := &SecBufferDesc{ + Version: SECBUFFER_VERSION, + BuffersCount: 1, + Buffers: inBuf, + } + + outBuf := &SecBuffer{ + BufferType: SECBUFFER_TOKEN, + } + if len(dst) > 0 { + outBuf.BufferSize = uint32(len(dst)) + outBuf.Buffer = &dst[0] + } + outBufs := &SecBufferDesc{ + Version: SECBUFFER_VERSION, + BuffersCount: 1, + Buffers: outBuf, + } + + h := c.Handle + if c.Handle == nil { + c.Handle = &c.handle + } + + ret := c.updFn(c, h, c.Handle, outBufs, inBufs) + switch ret { + case SEC_E_OK: + // session established -> return success + return true, int(outBuf.BufferSize), nil + case SEC_I_COMPLETE_NEEDED, SEC_I_COMPLETE_AND_CONTINUE: + ret = CompleteAuthToken(c.Handle, outBufs) + if ret != SEC_E_OK { + return false, 0, ret + } + case SEC_I_CONTINUE_NEEDED: + default: + return false, 0, ret + } + return false, int(outBuf.BufferSize), nil +} + +func (c *Context) Release() error { + ret := DeleteSecurityContext(c.Handle) + if ret != SEC_E_OK { + return ret + } + return nil +} + +func (c *Context) Expiry() time.Time { + return time.Unix(0, c.expiry.Nanoseconds()) +} + +// TODO: add comment to function doco that this "impersonation" is applied to current OS thread. +func (c *Context) ImpersonateUser() error { + ret := ImpersonateSecurityContext(c.Handle) + if ret != SEC_E_OK { + return ret + } + return nil +} + +func (c *Context) RevertToSelf() error { + ret := RevertSecurityContext(c.Handle) + if ret != SEC_E_OK { + return ret + } + return nil +} diff --git a/sspi_test.go b/sspi_test.go new file mode 100644 index 0000000..cc70e18 --- /dev/null +++ b/sspi_test.go @@ -0,0 +1,31 @@ +// 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 windows + +package sspi_test + +import ( + "sspi" + "testing" +) + +func TestQueryPackageInfo(t *testing.T) { + pkgnames := []string{ + sspi.NTLMSP_NAME, + sspi.MICROSOFT_KERBEROS_NAME, + sspi.NEGOSSP_NAME, + } + for _, name := range pkgnames { + pi, err := sspi.QueryPackageInfo(name) + if err != nil { + t.Error(err) + continue + } + if pi.Name != name { + t.Errorf("unexpected package name %q returned for %q package: package info is %#v", pi.Name, name, pi) + continue + } + } +} diff --git a/syscall.go b/syscall.go new file mode 100644 index 0000000..d50ad05 --- /dev/null +++ b/syscall.go @@ -0,0 +1,128 @@ +// 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 windows + +package sspi + +import ( + "syscall" +) + +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall.go + +const ( + SEC_E_OK = syscall.Errno(0) + + SEC_I_COMPLETE_AND_CONTINUE = syscall.Errno(590612) + SEC_I_COMPLETE_NEEDED = syscall.Errno(590611) + SEC_I_CONTINUE_NEEDED = syscall.Errno(590610) + + NTLMSP_NAME = "NTLM" + MICROSOFT_KERBEROS_NAME = "Kerberos" + NEGOSSP_NAME = "Negotiate" +) + +type SecPkgInfo struct { + Capabilities uint32 + Version uint16 + RPCID uint16 + MaxToken uint32 + Name *uint16 + Comment *uint16 +} + +//sys QuerySecurityPackageInfo(pkgname *uint16, pkginfo **SecPkgInfo) (ret syscall.Errno) = secur32.QuerySecurityPackageInfoW +//sys FreeContextBuffer(buf *byte) (ret syscall.Errno) = secur32.FreeContextBuffer + +const ( + SECPKG_CRED_INBOUND = 1 + SECPKG_CRED_OUTBOUND = 2 + SECPKG_CRED_BOTH = (SECPKG_CRED_OUTBOUND | SECPKG_CRED_INBOUND) +) + +type LUID struct { + LowPart uint32 + HighPart int32 +} + +type CredHandle struct { + Lower uintptr + Upper uintptr +} + +//sys AcquireCredentialsHandle(principal *uint16, pkgname *uint16, creduse uint32, logonid *LUID, authdata *byte, getkeyfn uintptr, getkeyarg uintptr, handle *CredHandle, expiry *syscall.Filetime) (ret syscall.Errno) = secur32.AcquireCredentialsHandleW +//sys FreeCredentialsHandle(handle *CredHandle) (ret syscall.Errno) = secur32.FreeCredentialsHandle + +const ( + SECURITY_NATIVE_DREP = 16 + + SECBUFFER_TOKEN = 2 + SECBUFFER_PKG_PARAMS = 3 + SECBUFFER_MISSING = 4 + SECBUFFER_EXTRA = 5 + SECBUFFER_STREAM_TRAILER = 6 + SECBUFFER_STREAM_HEADER = 7 + SECBUFFER_PADDING = 9 + SECBUFFER_STREAM = 10 + SECBUFFER_READONLY = 0x80000000 + SECBUFFER_ATTRMASK = 0xf0000000 + SECBUFFER_VERSION = 0 + + ISC_REQ_DELEGATE = 1 + ISC_REQ_MUTUAL_AUTH = 2 + ISC_REQ_REPLAY_DETECT = 4 + ISC_REQ_SEQUENCE_DETECT = 8 + ISC_REQ_CONFIDENTIALITY = 16 + ISC_REQ_USE_SESSION_KEY = 32 + ISC_REQ_PROMPT_FOR_CREDS = 64 + ISC_REQ_USE_SUPPLIED_CREDS = 128 + ISC_REQ_ALLOCATE_MEMORY = 256 + ISC_REQ_USE_DCE_STYLE = 512 + ISC_REQ_DATAGRAM = 1024 + ISC_REQ_CONNECTION = 2048 + ISC_REQ_EXTENDED_ERROR = 16384 + ISC_REQ_STREAM = 32768 + ISC_REQ_INTEGRITY = 65536 + ISC_REQ_MANUAL_CRED_VALIDATION = 524288 + ISC_REQ_HTTP = 268435456 + + ASC_REQ_DELEGATE = 1 + ASC_REQ_MUTUAL_AUTH = 2 + ASC_REQ_REPLAY_DETECT = 4 + ASC_REQ_SEQUENCE_DETECT = 8 + ASC_REQ_CONFIDENTIALITY = 16 + ASC_REQ_USE_SESSION_KEY = 32 + ASC_REQ_ALLOCATE_MEMORY = 256 + ASC_REQ_USE_DCE_STYLE = 512 + ASC_REQ_DATAGRAM = 1024 + ASC_REQ_CONNECTION = 2048 + ASC_REQ_EXTENDED_ERROR = 32768 + ASC_REQ_STREAM = 65536 + ASC_REQ_INTEGRITY = 131072 +) + +type CtxtHandle struct { + Lower uintptr + Upper uintptr +} + +type SecBuffer struct { + BufferSize uint32 + BufferType uint32 + Buffer *byte +} + +type SecBufferDesc struct { + Version uint32 + BuffersCount uint32 + Buffers *SecBuffer +} + +//sys InitializeSecurityContext(credential *CredHandle, context *CtxtHandle, targname *uint16, contextreq uint32, reserved1 uint32, targdatarep uint32, input *SecBufferDesc, reserved2 uint32, newcontext *CtxtHandle, output *SecBufferDesc, contextattr *uint32, expiry *syscall.Filetime) (ret syscall.Errno) = secur32.InitializeSecurityContextW +//sys AcceptSecurityContext(credential *CredHandle, context *CtxtHandle, input *SecBufferDesc, contextreq uint32, targdatarep uint32, newcontext *CtxtHandle, output *SecBufferDesc, contextattr *uint32, expiry *syscall.Filetime) (ret syscall.Errno) = secur32.AcceptSecurityContext +//sys CompleteAuthToken(context *CtxtHandle, token *SecBufferDesc) (ret syscall.Errno) = secur32.CompleteAuthToken +//sys DeleteSecurityContext(context *CtxtHandle) (ret syscall.Errno) = secur32.DeleteSecurityContext +//sys ImpersonateSecurityContext(context *CtxtHandle) (ret syscall.Errno) = secur32.ImpersonateSecurityContext +//sys RevertSecurityContext(context *CtxtHandle) (ret syscall.Errno) = secur32.RevertSecurityContext diff --git a/zsyscall_windows.go b/zsyscall_windows.go new file mode 100644 index 0000000..197531f --- /dev/null +++ b/zsyscall_windows.go @@ -0,0 +1,83 @@ +// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT + +package sspi + +import "unsafe" +import "syscall" + +var _ unsafe.Pointer + +var ( + modsecur32 = syscall.NewLazyDLL("secur32.dll") + + procQuerySecurityPackageInfoW = modsecur32.NewProc("QuerySecurityPackageInfoW") + procFreeContextBuffer = modsecur32.NewProc("FreeContextBuffer") + procAcquireCredentialsHandleW = modsecur32.NewProc("AcquireCredentialsHandleW") + procFreeCredentialsHandle = modsecur32.NewProc("FreeCredentialsHandle") + procInitializeSecurityContextW = modsecur32.NewProc("InitializeSecurityContextW") + procAcceptSecurityContext = modsecur32.NewProc("AcceptSecurityContext") + procCompleteAuthToken = modsecur32.NewProc("CompleteAuthToken") + procDeleteSecurityContext = modsecur32.NewProc("DeleteSecurityContext") + procImpersonateSecurityContext = modsecur32.NewProc("ImpersonateSecurityContext") + procRevertSecurityContext = modsecur32.NewProc("RevertSecurityContext") +) + +func QuerySecurityPackageInfo(pkgname *uint16, pkginfo **SecPkgInfo) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall(procQuerySecurityPackageInfoW.Addr(), 2, uintptr(unsafe.Pointer(pkgname)), uintptr(unsafe.Pointer(pkginfo)), 0) + ret = syscall.Errno(r0) + return +} + +func FreeContextBuffer(buf *byte) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall(procFreeContextBuffer.Addr(), 1, uintptr(unsafe.Pointer(buf)), 0, 0) + ret = syscall.Errno(r0) + return +} + +func AcquireCredentialsHandle(principal *uint16, pkgname *uint16, creduse uint32, logonid *LUID, authdata *byte, getkeyfn uintptr, getkeyarg uintptr, handle *CredHandle, expiry *syscall.Filetime) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall9(procAcquireCredentialsHandleW.Addr(), 9, uintptr(unsafe.Pointer(principal)), uintptr(unsafe.Pointer(pkgname)), uintptr(creduse), uintptr(unsafe.Pointer(logonid)), uintptr(unsafe.Pointer(authdata)), uintptr(getkeyfn), uintptr(getkeyarg), uintptr(unsafe.Pointer(handle)), uintptr(unsafe.Pointer(expiry))) + ret = syscall.Errno(r0) + return +} + +func FreeCredentialsHandle(handle *CredHandle) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall(procFreeCredentialsHandle.Addr(), 1, uintptr(unsafe.Pointer(handle)), 0, 0) + ret = syscall.Errno(r0) + return +} + +func InitializeSecurityContext(credential *CredHandle, context *CtxtHandle, targname *uint16, contextreq uint32, reserved1 uint32, targdatarep uint32, input *SecBufferDesc, reserved2 uint32, newcontext *CtxtHandle, output *SecBufferDesc, contextattr *uint32, expiry *syscall.Filetime) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall12(procInitializeSecurityContextW.Addr(), 12, uintptr(unsafe.Pointer(credential)), uintptr(unsafe.Pointer(context)), uintptr(unsafe.Pointer(targname)), uintptr(contextreq), uintptr(reserved1), uintptr(targdatarep), uintptr(unsafe.Pointer(input)), uintptr(reserved2), uintptr(unsafe.Pointer(newcontext)), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(contextattr)), uintptr(unsafe.Pointer(expiry))) + ret = syscall.Errno(r0) + return +} + +func AcceptSecurityContext(credential *CredHandle, context *CtxtHandle, input *SecBufferDesc, contextreq uint32, targdatarep uint32, newcontext *CtxtHandle, output *SecBufferDesc, contextattr *uint32, expiry *syscall.Filetime) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall9(procAcceptSecurityContext.Addr(), 9, uintptr(unsafe.Pointer(credential)), uintptr(unsafe.Pointer(context)), uintptr(unsafe.Pointer(input)), uintptr(contextreq), uintptr(targdatarep), uintptr(unsafe.Pointer(newcontext)), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(contextattr)), uintptr(unsafe.Pointer(expiry))) + ret = syscall.Errno(r0) + return +} + +func CompleteAuthToken(context *CtxtHandle, token *SecBufferDesc) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall(procCompleteAuthToken.Addr(), 2, uintptr(unsafe.Pointer(context)), uintptr(unsafe.Pointer(token)), 0) + ret = syscall.Errno(r0) + return +} + +func DeleteSecurityContext(context *CtxtHandle) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall(procDeleteSecurityContext.Addr(), 1, uintptr(unsafe.Pointer(context)), 0, 0) + ret = syscall.Errno(r0) + return +} + +func ImpersonateSecurityContext(context *CtxtHandle) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall(procImpersonateSecurityContext.Addr(), 1, uintptr(unsafe.Pointer(context)), 0, 0) + ret = syscall.Errno(r0) + return +} + +func RevertSecurityContext(context *CtxtHandle) (ret syscall.Errno) { + r0, _, _ := syscall.Syscall(procRevertSecurityContext.Addr(), 1, uintptr(unsafe.Pointer(context)), 0, 0) + ret = syscall.Errno(r0) + return +}