Merge pull request #2871 from stffabi/sspi-ntlm

NTLM authentication with SSPI on windows
This commit is contained in:
Taylor Blau 2018-02-27 14:39:03 -08:00 committed by GitHub
commit 837a0dd046
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 3039 additions and 95 deletions

2
debian/rules vendored

@ -17,7 +17,7 @@ endif
BUILD_DIR := obj-$(DEB_HOST_GNU_TYPE)
export DH_GOPKG := github.com/git-lfs/git-lfs
# DH_GOLANG_EXCLUDES typically incorporates vendor exclusions from script/test
export DH_GOLANG_EXCLUDES := test github.com/olekukonko/ts/* github.com/xeipuuv/* github.com/spf13/cobra/* github.com/kr/* github.com/pkg/errors
export DH_GOLANG_EXCLUDES := test github.com/olekukonko/ts/* github.com/xeipuuv/* github.com/spf13/cobra/* github.com/kr/* github.com/pkg/errors github.com/alexbrainman/sspi/*
export PATH := $(CURDIR)/$(BUILD_DIR)/bin:$(PATH)
# by-default, dh_golang only copies *.go and other source - this upsets a bunch of vendor test routines

24
glide.lock generated

@ -1,10 +1,18 @@
hash: e19b925b9eaca9a10a7742b4a4b1dc8047bff437584538dda59f4f10e69fa6ca
updated: 2018-01-29T17:36:52.158516-08:00
hash: 61a26ad942b6cdacffd945003b76991b4caeb6a16baab8e86943f6bbc83410e9
updated: 2018-02-15T23:41:56.5827516+01:00
imports:
- name: github.com/alexbrainman/sspi
version: 4729b3d4d8581b2db83864d1018926e4154f9406
subpackages:
- ntlm
- name: github.com/bgentry/go-netrc
version: 9fd32a8b3d3d3f9d43c341bfe098430e07609480
subpackages:
- netrc
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/kr/pty
@ -13,6 +21,10 @@ imports:
version: ecf753e7c962639ab5a1fb46f7da627d4c0a04b8
- name: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/rubyist/tracerx
version: 787959303086f44a8c361240dfac53d3e9d53ed2
- name: github.com/spf13/cobra
@ -32,14 +44,6 @@ imports:
- name: github.com/xeipuuv/gojsonschema
version: 6b67b3fab74d992bd07f72550006ab2c6907c416
testImports:
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/xeipuuv/gojsonpointer
version: 4e3ac2762d5f479393488629ee9370b50873b3a6
- name: github.com/xeipuuv/gojsonreference

@ -26,3 +26,7 @@ import:
version: 6b67b3fab74d992bd07f72550006ab2c6907c416
- package: github.com/pkg/errors
version: c605e284fe17294bda444b34710735b29d1a9d90
- package: github.com/alexbrainman/sspi
version: 4729b3d4d8581b2db83864d1018926e4154f9406
subpackages:
- ntlm

@ -9,10 +9,15 @@ import (
"net/url"
"strings"
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
"github.com/git-lfs/git-lfs/errors"
)
type ntmlCredentials struct {
domain string
username string
password string
}
func (c *Client) doWithNTLM(req *http.Request, credHelper CredentialHelper, creds Creds, credsURL *url.URL) (*http.Response, error) {
res, err := c.do(req)
if err != nil && !errors.IsAuthError(err) {
@ -28,25 +33,13 @@ func (c *Client) doWithNTLM(req *http.Request, credHelper CredentialHelper, cred
// If the status is 401 then we need to re-authenticate
func (c *Client) ntlmReAuth(req *http.Request, credHelper CredentialHelper, creds Creds, retry bool) (*http.Response, error) {
body, err := rewoundRequestBody(req)
ntmlCreds, err := ntlmGetCredentials(creds)
if err != nil {
return nil, err
}
req.Body = body
chRes, challengeMsg, err := c.ntlmNegotiate(req, ntlmNegotiateMessage)
if err != nil {
return chRes, err
}
body, err = rewoundRequestBody(req)
if err != nil {
return nil, err
}
req.Body = body
res, err := c.ntlmChallenge(req, challengeMsg, creds)
if err != nil {
res, err := c.ntlmAuthenticateRequest(req, ntmlCreds)
if err != nil && !errors.IsAuthError(err) {
return res, err
}
@ -67,9 +60,8 @@ func (c *Client) ntlmReAuth(req *http.Request, credHelper CredentialHelper, cred
return res, nil
}
func (c *Client) ntlmNegotiate(req *http.Request, message string) (*http.Response, []byte, error) {
req.Header.Add("Authorization", message)
res, err := c.do(req)
func (c *Client) ntlmSendType1Message(req *http.Request, message []byte) (*http.Response, []byte, error) {
res, err := c.ntlmSendMessage(req, message)
if err != nil && !errors.IsAuthError(err) {
return res, nil, err
}
@ -81,56 +73,20 @@ func (c *Client) ntlmNegotiate(req *http.Request, message string) (*http.Respons
return res, by, err
}
func (c *Client) ntlmChallenge(req *http.Request, challengeBytes []byte, creds Creds) (*http.Response, error) {
challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
if err != nil {
return nil, err
}
session, err := c.ntlmClientSession(creds)
if err != nil {
return nil, err
}
session.ProcessChallengeMessage(challenge)
authenticate, err := session.GenerateAuthenticateMessage()
if err != nil {
return nil, err
}
authMsg := base64.StdEncoding.EncodeToString(authenticate.Bytes())
req.Header.Set("Authorization", "NTLM "+authMsg)
return c.do(req)
func (c *Client) ntlmSendType3Message(req *http.Request, authenticate []byte) (*http.Response, error) {
return c.ntlmSendMessage(req, authenticate)
}
func (c *Client) ntlmClientSession(creds Creds) (ntlm.ClientSession, error) {
c.ntlmMu.Lock()
defer c.ntlmMu.Unlock()
splits := strings.Split(creds["username"], "\\")
if len(splits) != 2 {
return nil, fmt.Errorf("Your user name must be of the form DOMAIN\\user. It is currently %s", creds["username"])
}
domain := strings.ToUpper(splits[0])
username := splits[1]
if c.ntlmSessions == nil {
c.ntlmSessions = make(map[string]ntlm.ClientSession)
}
if ses, ok := c.ntlmSessions[domain]; ok {
return ses, nil
}
session, err := ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
func (c *Client) ntlmSendMessage(req *http.Request, message []byte) (*http.Response, error) {
body, err := rewoundRequestBody(req)
if err != nil {
return nil, err
}
req.Body = body
session.SetUserInfo(username, creds["password"], strings.ToUpper(splits[0]))
c.ntlmSessions[domain] = session
return session, nil
msg := base64.StdEncoding.EncodeToString(message)
req.Header.Set("Authorization", "NTLM "+msg)
return c.do(req)
}
func parseChallengeResponse(res *http.Response) ([]byte, error) {
@ -163,4 +119,21 @@ func rewoundRequestBody(req *http.Request) (io.ReadCloser, error) {
return body, err
}
const ntlmNegotiateMessage = "NTLM TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB"
func ntlmGetCredentials(creds Creds) (*ntmlCredentials, error) {
username := creds["username"]
password := creds["password"]
if username == "" && password == "" {
return nil, nil
}
splits := strings.Split(username, "\\")
if len(splits) != 2 {
return nil, fmt.Errorf("Your user name must be of the form DOMAIN\\user. It is currently '%s'", username)
}
domain := strings.ToUpper(splits[0])
username = splits[1]
return &ntmlCredentials{domain: domain, username: username, password: password}, nil
}

69
lfsapi/ntlm_auth_nix.go Normal file

@ -0,0 +1,69 @@
// +build !windows
package lfsapi
import (
"encoding/base64"
"fmt"
"net/http"
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
)
func (c *Client) ntlmAuthenticateRequest(req *http.Request, creds *ntmlCredentials) (*http.Response, error) {
negotiate, err := base64.StdEncoding.DecodeString(ntlmNegotiateMessage)
if err != nil {
return nil, err
}
chRes, challengeMsg, err := c.ntlmSendType1Message(req, negotiate)
if err != nil {
return chRes, err
}
challenge, err := ntlm.ParseChallengeMessage(challengeMsg)
if err != nil {
return nil, err
}
session, err := c.ntlmClientSession(creds)
if err != nil {
return nil, err
}
session.ProcessChallengeMessage(challenge)
authenticate, err := session.GenerateAuthenticateMessage()
if err != nil {
return nil, err
}
return c.ntlmSendType3Message(req, authenticate.Bytes())
}
func (c *Client) ntlmClientSession(creds *ntmlCredentials) (ntlm.ClientSession, error) {
c.ntlmMu.Lock()
defer c.ntlmMu.Unlock()
if creds == nil {
return nil, fmt.Errorf("Your user name must be of the form DOMAIN\\user. Single-sign-on is not supported.")
}
if c.ntlmSessions == nil {
c.ntlmSessions = make(map[string]ntlm.ClientSession)
}
if ses, ok := c.ntlmSessions[creds.domain]; ok {
return ses, nil
}
session, err := ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
if err != nil {
return nil, err
}
session.SetUserInfo(creds.username, creds.password, creds.domain)
c.ntlmSessions[creds.domain] = session
return session, nil
}
const ntlmNegotiateMessage = "TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB"

@ -0,0 +1,36 @@
// +build !windows
package lfsapi
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNtlmClientSession(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
creds := &ntmlCredentials{domain: "MOOSEDOMAIN", username: "canadian", password: "MooseAntlersYeah"}
session1, err := cli.ntlmClientSession(creds)
assert.Nil(t, err)
assert.NotNil(t, session1)
// The second call should ignore creds and give the session we just created.
badCreds := &ntmlCredentials{domain: "MOOSEDOMAIN", username: "badusername", password: "MooseAntlersYeah"}
session2, err := cli.ntlmClientSession(badCreds)
assert.Nil(t, err)
assert.NotNil(t, session2)
assert.EqualValues(t, session1, session2)
}
func TestNtlmClientSessionBadCreds(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
// Single-Sign-On is not supported on *nix
_, err = cli.ntlmClientSession(nil)
assert.NotNil(t, err)
}

@ -0,0 +1,43 @@
// +build windows
package lfsapi
import (
"net/http"
"github.com/alexbrainman/sspi"
"github.com/alexbrainman/sspi/ntlm"
)
func (c *Client) ntlmAuthenticateRequest(req *http.Request, creds *ntmlCredentials) (*http.Response, error) {
var sspiCreds *sspi.Credentials
var err error
if creds == nil {
sspiCreds, err = ntlm.AcquireCurrentUserCredentials()
} else {
sspiCreds, err = ntlm.AcquireUserCredentials(creds.domain, creds.username, creds.password)
}
if err != nil {
return nil, err
}
defer sspiCreds.Release()
secctx, negotiate, err := ntlm.NewClientContext(sspiCreds)
if err != nil {
return nil, err
}
defer secctx.Release()
chRes, challengeMsg, err := c.ntlmSendType1Message(req, negotiate)
if err != nil {
return chRes, err
}
authenticateMsg, err := secctx.Update(challengeMsg)
if err != nil {
return nil, err
}
return c.ntlmSendType3Message(req, authenticateMsg)
}

@ -15,7 +15,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestNTLMAuth(t *testing.T) {
func TestNtlmAuth(t *testing.T) {
session, err := ntlm.CreateServerSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
require.Nil(t, err)
session.SetUserInfo("ntlmuser", "ntlmpass", "NTLMDOMAIN")
@ -36,12 +36,21 @@ func TestNTLMAuth(t *testing.T) {
assert.Equal(t, "ntlm", string(by))
}
switch authHeader {
case "":
switch called {
case 1:
w.Header().Set("Www-Authenticate", "ntlm")
w.WriteHeader(401)
case ntlmNegotiateMessage:
case 2:
assert.True(t, strings.HasPrefix(req.Header.Get("Authorization"), "NTLM "))
neg := authHeader[5:] // strip "ntlm " prefix
_, err := base64.StdEncoding.DecodeString(neg)
if !assert.Nil(t, err) {
t.Logf("neg base64 error: %+v", err)
w.WriteHeader(500)
return
}
// ntlm implementation can't currently parse the negotiate message
ch, err := session.GenerateChallengeMessage()
if !assert.Nil(t, err) {
t.Logf("challenge gen error: %+v", err)
@ -62,13 +71,21 @@ func TestNTLMAuth(t *testing.T) {
return
}
_, err = ntlm.ParseAuthenticateMessage(val, 2)
authMsg, err := ntlm.ParseAuthenticateMessage(val, 2)
if !assert.Nil(t, err) {
t.Logf("auth parse error: %+v", err)
w.WriteHeader(500)
return
}
err = session.ProcessAuthenticateMessage(authMsg)
if !assert.Nil(t, err) {
t.Logf("auth process error: %+v", err)
w.WriteHeader(500)
return
}
w.WriteHeader(200)
}
}))
defer srv.Close()
@ -101,28 +118,24 @@ func TestNTLMAuth(t *testing.T) {
assert.True(t, credHelper.IsApproved(creds))
}
func TestNtlmClientSession(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
func TestNtlmGetCredentials(t *testing.T) {
creds := Creds{"username": "MOOSEDOMAIN\\canadian", "password": "MooseAntlersYeah"}
session1, err := cli.ntlmClientSession(creds)
ntmlCreds, err := ntlmGetCredentials(creds)
assert.Nil(t, err)
assert.NotNil(t, session1)
assert.NotNil(t, ntmlCreds)
assert.Equal(t, "MOOSEDOMAIN", ntmlCreds.domain)
assert.Equal(t, "canadian", ntmlCreds.username)
assert.Equal(t, "MooseAntlersYeah", ntmlCreds.password)
// The second call should ignore creds and give the session we just created.
badCreds := Creds{"username": "MOOSEDOMAIN\\badusername", "password": "MooseAntlersYeah"}
session2, err := cli.ntlmClientSession(badCreds)
creds = Creds{"username": "", "password": ""}
ntmlCreds, err = ntlmGetCredentials(creds)
assert.Nil(t, err)
assert.NotNil(t, session2)
assert.EqualValues(t, session1, session2)
assert.Nil(t, ntmlCreds)
}
func TestNtlmClientSessionBadCreds(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
func TestNtlmGetCredentialsBadCreds(t *testing.T) {
creds := Creds{"username": "badusername", "password": "MooseAntlersYeah"}
_, err = cli.ntlmClientSession(creds)
_, err := ntlmGetCredentials(creds)
assert.NotNil(t, err)
}

@ -16,6 +16,7 @@ if [ $# -eq 0 ]; then
| grep -v "github.com/stretchr/testify" \
| grep -v "github.com/xeipuuv/gojsonreference" \
| grep -v "github.com/xeipuuv/gojsonschema" \
| grep -v "github.com/alexbrainman/sspi" \
)"
else
PACKAGES="$(echo "$PACKAGES" \

27
vendor/github.com/alexbrainman/sspi/LICENSE generated vendored Normal file

@ -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.

1
vendor/github.com/alexbrainman/sspi/README.md generated vendored Normal file

@ -0,0 +1 @@
This repository holds Go packages for accessing Security Support Provider Interface on Windows.

57
vendor/github.com/alexbrainman/sspi/buffer.go generated vendored Normal file

@ -0,0 +1,57 @@
// 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 (
"io"
"unsafe"
)
func (b *SecBuffer) Set(buftype uint32, data []byte) {
b.BufferType = buftype
if len(data) > 0 {
b.Buffer = &data[0]
b.BufferSize = uint32(len(data))
} else {
b.Buffer = nil
b.BufferSize = 0
}
}
func (b *SecBuffer) Free() error {
if b.Buffer == nil {
return nil
}
return FreeContextBuffer((*byte)(unsafe.Pointer(b.Buffer)))
}
func (b *SecBuffer) Bytes() []byte {
if b.Buffer == nil || b.BufferSize <= 0 {
return nil
}
return (*[2 << 20]byte)(unsafe.Pointer(b.Buffer))[:b.BufferSize]
}
func (b *SecBuffer) WriteAll(w io.Writer) (int, error) {
if b.BufferSize == 0 || b.Buffer == nil {
return 0, nil
}
data := b.Bytes()
total := 0
for {
n, err := w.Write(data)
total += n
if err != nil {
return total, err
}
if n >= len(data) {
break
}
data = data[n:]
}
return total, nil
}

7
vendor/github.com/alexbrainman/sspi/mksyscall.go generated vendored Normal file

@ -0,0 +1,7 @@
// Copyright 2018 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 sspi
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -systemdll=false -output=zsyscall_windows.go syscall.go

@ -0,0 +1,283 @@
// 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.
// +build windows
package negotiate_test
import (
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/alexbrainman/sspi/negotiate"
)
var (
testURL = flag.String("url", "", "server URL for TestNegotiateHTTPClient")
)
// TODO: perhaps add Transport that is similar to http.Transport
// TODO: perhaps implement separate NTLMTransport and KerberosTransport (not sure about this idea)
// TODO: KerberosTransport is (I beleive) sinlge leg protocol, so it can be implemented easily (unlike NTLM)
// TODO: perhaps implement both server and client Transport
type httpClient struct {
client *http.Client
transport *http.Transport
url string
}
func newHTTPClient(url string) *httpClient {
transport := &http.Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
}
return &httpClient{
client: &http.Client{Transport: transport},
transport: transport,
url: url,
}
}
func (c *httpClient) CloseIdleConnections() {
c.transport.CloseIdleConnections()
}
func (c *httpClient) get(req *http.Request) (*http.Response, string, error) {
res, err := c.client.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 (c *httpClient) canDoNegotiate() error {
req, err := http.NewRequest("GET", c.url, nil)
if err != nil {
return err
}
res, _, err := c.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 == "Negotiate" {
return nil
}
}
return fmt.Errorf("Www-Authenticate header does not contain Negotiate, but has %v", authHeaders)
}
func findAuthHeader(res *http.Response) ([]byte, error) {
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]) < 10 {
return nil, fmt.Errorf("Www-Authenticate header is to short: %q", authHeaders[0])
}
if !strings.HasPrefix(authHeaders[0], "Negotiate ") {
return nil, fmt.Errorf("Www-Authenticate header is suppose to starts with \"Negotiate \", but is %q", authHeaders[0])
}
token, err := base64.StdEncoding.DecodeString(authHeaders[0][10:])
if err != nil {
return nil, err
}
return token, nil
}
func (c *httpClient) startAuthorization(inputToken []byte) ([]byte, error) {
req, err := http.NewRequest("GET", c.url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(inputToken))
res, _, err := c.get(req)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusUnauthorized {
return nil, fmt.Errorf("Unauthorized expected, but got %v", res.StatusCode)
}
outputToken, err := findAuthHeader(res)
if err != nil {
return nil, err
}
return outputToken, nil
}
func (c *httpClient) completeAuthorization(inputToken []byte) (*http.Response, string, error) {
req, err := http.NewRequest("GET", c.url, nil)
if err != nil {
return nil, "", err
}
req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(inputToken))
res, body, err := c.get(req)
if err != nil {
return nil, "", err
}
if res.StatusCode != http.StatusOK {
return nil, "", fmt.Errorf("OK expected, but got %v", res.StatusCode)
}
return res, 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 := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
secctx, clientToken1, err := negotiate.NewClientContext(cred, "")
if err != nil {
t.Fatal(err)
}
defer secctx.Release()
client := newHTTPClient(*testURL)
defer client.CloseIdleConnections()
err = client.canDoNegotiate()
if err != nil {
t.Fatal(err)
}
serverToken1, err := client.startAuthorization(clientToken1)
if err != nil {
t.Fatal(err)
}
authCompleted, clientToken2, err := secctx.Update(serverToken1)
if err != nil {
t.Fatal(err)
}
if len(clientToken2) == 0 {
t.Fatal("secctx.Update returns empty token for the peer, but our authentication is not done yet")
}
res, _, err := client.completeAuthorization(clientToken2)
if err != nil {
t.Fatal(err)
}
if authCompleted {
return
}
serverToken2, err := findAuthHeader(res)
if err != nil {
t.Fatal(err)
}
authCompleted, lastToken, err := secctx.Update(serverToken2)
if err != nil {
t.Fatal(err)
}
if !authCompleted {
t.Fatal("client authentication should be completed now")
}
if len(lastToken) > 0 {
t.Fatalf("last token supposed to be empty, but %v returned", lastToken)
}
}
func TestKerberosHTTPClient(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")
}
u, err := url.Parse(*testURL)
if err != nil {
t.Fatal(err)
}
targetName := "http/" + strings.ToUpper(u.Host)
cred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
secctx, token, err := negotiate.NewClientContext(cred, targetName)
if err != nil {
t.Fatal(err)
}
defer secctx.Release()
client := newHTTPClient(*testURL)
defer client.CloseIdleConnections()
err = client.canDoNegotiate()
if err != nil {
t.Fatal(err)
}
res, _, err := client.completeAuthorization(token)
if err != nil {
t.Fatal(err)
}
serverToken, err := findAuthHeader(res)
if err != nil {
t.Fatal(err)
}
authCompleted, lastToken, err := secctx.Update(serverToken)
if err != nil {
t.Fatal(err)
}
if !authCompleted {
t.Fatal("client authentication should be completed now")
}
if len(lastToken) > 0 {
t.Fatalf("last token supposed to be empty, but %v returned", lastToken)
}
}
// TODO: See http://www.innovation.ch/personal/ronald/ntlm.html#connections about needed to keep connection alive during authentication.
func TestNegotiateHTTPServer(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO: implement Negotiate authentication here
w.Write([]byte("hello"))
}))
defer ts.Close()
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))
}
}

@ -0,0 +1,348 @@
// 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.
// +build windows
// Package negotiate provides access to the Microsoft Negotiate SSP Package.
//
package negotiate
import (
"errors"
"syscall"
"time"
"unsafe"
"github.com/alexbrainman/sspi"
)
// TODO: maybe (if possible) move all winapi related out of sspi and into sspi/internal/winapi
// PackageInfo contains Negotiate SSP package description.
var PackageInfo *sspi.PackageInfo
func init() {
var err error
PackageInfo, err = sspi.QueryPackageInfo(sspi.NEGOSSP_NAME)
if err != nil {
panic("failed to fetch Negotiate package info: " + err.Error())
}
}
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)))
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)
}
// TODO: see if I can share this common ntlm and negotiate code
// 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 := sspi.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: sspi.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)
}
func updateContext(c *sspi.Context, dst, src []byte, targetName *uint16) (authCompleted bool, n int, err error) {
var inBuf, outBuf [1]sspi.SecBuffer
inBuf[0].Set(sspi.SECBUFFER_TOKEN, src)
inBufs := &sspi.SecBufferDesc{
Version: sspi.SECBUFFER_VERSION,
BuffersCount: 1,
Buffers: &inBuf[0],
}
outBuf[0].Set(sspi.SECBUFFER_TOKEN, dst)
outBufs := &sspi.SecBufferDesc{
Version: sspi.SECBUFFER_VERSION,
BuffersCount: 1,
Buffers: &outBuf[0],
}
ret := c.Update(targetName, outBufs, inBufs)
switch ret {
case sspi.SEC_E_OK:
// session established -> return success
return true, int(outBuf[0].BufferSize), nil
case sspi.SEC_I_COMPLETE_NEEDED, sspi.SEC_I_COMPLETE_AND_CONTINUE:
ret = sspi.CompleteAuthToken(c.Handle, outBufs)
if ret != sspi.SEC_E_OK {
return false, 0, ret
}
case sspi.SEC_I_CONTINUE_NEEDED:
default:
return false, 0, ret
}
return false, int(outBuf[0].BufferSize), nil
}
func makeSignature(c *sspi.Context, msg []byte, qop, seqno uint32) ([]byte, error) {
_, maxSignature, _, _, 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 [2]sspi.SecBuffer
b[0].Set(sspi.SECBUFFER_DATA, msg)
b[1].Set(sspi.SECBUFFER_TOKEN, make([]byte, maxSignature))
ret := sspi.MakeSignature(c.Handle, qop, sspi.NewSecBufferDesc(b[:]), seqno)
if ret != sspi.SEC_E_OK {
return nil, ret
}
return 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)
b[1].Set(sspi.SECBUFFER_TOKEN, token)
var qop uint32
ret := sspi.VerifySignature(c.Handle, sspi.NewSecBufferDesc(b[:]), seqno, &qop)
if ret != sspi.SEC_E_OK {
return 0, ret
}
return qop, nil
}
// ClientContext is used by the client to manage all steps of Negotiate negotiation.
type ClientContext struct {
sctxt *sspi.Context
targetName *uint16
}
// NewClientContext creates new client context. It uses client
// credentials cred generated by AcquireCurrentUserCredentials or
// AcquireUserCredentials and SPN to start client Negotiate
// negotiation sequence. targetName is the service principal name
// (SPN) or the security context of the destination server.
// NewClientContext returns new token to be sent to the server.
func NewClientContext(cred *sspi.Credentials, targetName string) (cc *ClientContext, outputToken []byte, err error) {
var tname *uint16
if len(targetName) > 0 {
p, err2 := syscall.UTF16FromString(targetName)
if err2 != nil {
return nil, nil, err2
}
if len(p) > 0 {
tname = &p[0]
}
}
otoken := make([]byte, PackageInfo.MaxToken)
c := sspi.NewClientContext(cred, sspi.ISC_REQ_CONNECTION)
authCompleted, n, err2 := updateContext(c, otoken, nil, tname)
if err2 != nil {
return nil, nil, err2
}
if authCompleted {
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")
}
otoken = otoken[:n]
return &ClientContext{sctxt: c, targetName: tname}, otoken, 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 advances client part of Negotiate negotiation c. It uses
// token received from the server and returns true if client part
// of authentication is complete. It also returns new token to be
// sent to the server.
func (c *ClientContext) Update(token []byte) (authCompleted bool, outputToken []byte, err error) {
otoken := make([]byte, PackageInfo.MaxToken)
authDone, n, err2 := updateContext(c.sctxt, otoken, token, c.targetName)
if err2 != nil {
return false, nil, err2
}
if n == 0 && !authDone {
return false, nil, errors.New("negotiate token should not be empty")
}
otoken = otoken[:n]
return authDone, otoken, nil
}
// Sizes queries the client context for the sizes used in per-message
// functions. It returns the maximum token size used in authentication
// exchanges, the maximum signature size, the preferred integral size of
// messages, the size of any security trailer, and any error.
func (c *ClientContext) Sizes() (uint32, uint32, uint32, uint32, error) {
return c.sctxt.Sizes()
}
// MakeSignature uses the established client context to create a signature
// for the given message using the provided quality of protection flags and
// sequence number. It returns the signature token in addition to any error.
func (c *ClientContext) MakeSignature(msg []byte, qop, seqno uint32) ([]byte, error) {
return makeSignature(c.sctxt, msg, qop, seqno)
}
// VerifySignature uses the established client context and signature token
// to check that the provided message hasn't been tampered or received out
// of sequence. It returns any quality of protection flags and any error
// that occurred.
func (c *ClientContext) VerifySignature(msg, token []byte, seqno uint32) (uint32, error) {
return verifySignature(c.sctxt, msg, token, 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) {
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")
}
otoken = otoken[:n]
return &ServerContext{sctxt: c}, otoken, 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 advances server part of Negotiate negotiation c. It uses
// token received from the client and returns true if server part
// of authentication is complete. It also returns new token to be
// sent to the client.
func (c *ServerContext) Update(token []byte) (authCompleted bool, outputToken []byte, err error) {
otoken := make([]byte, PackageInfo.MaxToken)
authDone, n, err2 := updateContext(c.sctxt, otoken, token, nil)
if err2 != nil {
return false, nil, err2
}
if n == 0 && !authDone {
return false, nil, errors.New("negotiate token should not be empty")
}
otoken = otoken[:n]
return authDone, otoken, 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()
}
// Sizes queries the server context for the sizes used in per-message
// functions. It returns the maximum token size used in authentication
// exchanges, the maximum signature size, the preferred integral size of
// messages, the size of any security trailer, and any error.
func (c *ServerContext) Sizes() (uint32, uint32, uint32, uint32, error) {
return c.sctxt.Sizes()
}
// MakeSignature uses the established server context to create a signature
// for the given message using the provided quality of protection flags and
// sequence number. It returns the signature token in addition to any error.
func (c *ServerContext) MakeSignature(msg []byte, qop, seqno uint32) ([]byte, error) {
return makeSignature(c.sctxt, msg, qop, seqno)
}
// VerifySignature uses the established server context and signature token
// to check that the provided message hasn't been tampered or received out
// of sequence. It returns any quality of protection flags and any error
// that occurred.
func (c *ServerContext) VerifySignature(msg, token []byte, seqno uint32) (uint32, error) {
return verifySignature(c.sctxt, msg, token, seqno)
}

@ -0,0 +1,312 @@
// 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.
// +build windows
package negotiate_test
import (
"crypto/rand"
"flag"
"os"
"os/user"
"runtime"
"strings"
"syscall"
"testing"
"time"
"github.com/alexbrainman/sspi"
"github.com/alexbrainman/sspi/negotiate"
)
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 negotiate.PackageInfo.Name != "Negotiate" {
t.Fatalf(`invalid Negotiate package name of %q, "Negotiate" is expected.`, negotiate.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 expires in %v, more than 1 hour expected", name, validFor)
}
if validFor > 10*24*time.Hour {
t.Errorf("%v expires in %v, less than 10 days expected", name, validFor)
}
}
func testNegotiate(t *testing.T, clientCred *sspi.Credentials, SPN string) {
if len(SPN) == 0 {
t.Log("testing with blank SPN")
} else {
t.Logf("testing with SPN=%s", SPN)
}
serverCred, err := negotiate.AcquireServerCredentials()
if err != nil {
t.Fatal(err)
}
defer serverCred.Release()
client, toServerToken, err := negotiate.NewClientContext(clientCred, SPN)
if err != nil {
t.Fatal(err)
}
defer client.Release()
if len(toServerToken) == 0 {
t.Fatal("token for server cannot be empty")
}
t.Logf("sent %d bytes to server", len(toServerToken))
testContextExpiry(t, "client security context", client)
server, 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
for {
if len(toClientToken) == 0 {
break
}
t.Logf("sent %d bytes to client", len(toClientToken))
clientDone, toServerToken, err = client.Update(toClientToken)
if err != nil {
t.Fatal(err)
}
if len(toServerToken) == 0 {
break
}
t.Logf("sent %d bytes to server", len(toServerToken))
serverDone, toClientToken, err = server.Update(toServerToken)
if err != nil {
t.Fatal(err)
}
}
if !clientDone {
t.Fatal("client authentication should be completed now")
}
if !serverDone {
t.Fatal("server authentication should be completed now")
}
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 TestNegotiate(t *testing.T) {
cred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
testNegotiate(t, cred, "")
hostname, err := os.Hostname()
if err != nil {
t.Fatal(err)
}
testNegotiate(t, cred, "HOST/"+strings.ToUpper(hostname))
testNegotiate(t, cred, "HOST/127.0.0.1")
}
func TestNegotiateFailure(t *testing.T) {
clientCred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer clientCred.Release()
serverCred, err := negotiate.AcquireServerCredentials()
if err != nil {
t.Fatal(err)
}
defer serverCred.Release()
client, toServerToken, err := negotiate.NewClientContext(clientCred, "HOST/UNKNOWN_HOST_NAME")
if err != nil {
t.Fatal(err)
}
defer client.Release()
if len(toServerToken) == 0 {
t.Fatal("token for server cannot be empty")
}
t.Logf("sent %d bytes to server", len(toServerToken))
server, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
if err != nil {
t.Fatal(err)
}
defer server.Release()
for {
var clientDone, serverDone bool
if len(toClientToken) == 0 {
t.Fatal("token for client cannot be empty")
}
t.Logf("sent %d bytes to client", len(toClientToken))
clientDone, toServerToken, err = client.Update(toClientToken)
if err != nil {
t.Fatal(err)
}
t.Logf("clientDone=%v serverDone=%v", clientDone, serverDone)
if clientDone {
// t.Fatal("client authentication cannot be completed")
}
if len(toServerToken) == 0 {
t.Fatal("token for server cannot be empty")
}
t.Logf("sent %d bytes to server", len(toServerToken))
serverDone, toClientToken, err = server.Update(toServerToken)
if err != nil {
if err == sspi.SEC_E_LOGON_DENIED {
return
}
t.Fatalf("unexpected failure 0x%x: %v", uintptr(err.(syscall.Errno)), err)
}
t.Logf("clientDone=%v serverDone=%v", clientDone, serverDone)
if serverDone {
t.Fatal("server authentication cannot be completed")
}
}
}
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 := negotiate.AcquireUserCredentials(*testDomain, *testUsername, *testPassword)
if err != nil {
t.Fatal(err)
}
defer cred.Release()
testNegotiate(t, cred, "")
}
func TestSignature(t *testing.T) {
clientCred, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
t.Fatal(err)
}
defer clientCred.Release()
serverCred, err := negotiate.AcquireServerCredentials()
if err != nil {
t.Fatal(err)
}
defer serverCred.Release()
client, toServerToken, err := negotiate.NewClientContext(clientCred, "")
if err != nil {
t.Fatal(err)
}
defer client.Release()
if len(toServerToken) == 0 {
t.Fatal("token for server cannot be empty")
}
server, toClientToken, err := negotiate.NewServerContext(serverCred, toServerToken)
if err != nil {
t.Fatal(err)
}
defer server.Release()
var clientDone, serverDone bool
for {
if len(toClientToken) == 0 {
break
}
clientDone, toServerToken, err = client.Update(toClientToken)
if err != nil {
t.Fatal(err)
}
if len(toServerToken) == 0 {
break
}
serverDone, toClientToken, err = server.Update(toServerToken)
if err != nil {
t.Fatal(err)
}
}
if !clientDone {
t.Fatal("client authentication should be completed now")
}
if !serverDone {
t.Fatal("server authentication should be completed now")
}
clientMsg := make([]byte, 10)
_, err = rand.Read(clientMsg)
if err != nil {
t.Fatal(err)
}
t.Logf("clientMsg=%v", clientMsg)
clientSig, err := client.MakeSignature(clientMsg, 0, 0)
if err != nil {
t.Fatal(err)
}
t.Logf("clientSig=%v", clientSig)
_, err = server.VerifySignature(clientMsg, clientSig, 0)
if err != nil {
t.Fatal(err)
}
t.Logf("server verified client signature")
serverMsg := make([]byte, 10)
_, err = rand.Read(serverMsg)
if err != nil {
t.Fatal(err)
}
t.Logf("serverMsg=%v", serverMsg)
serverSig, err := server.MakeSignature(serverMsg, 0, 0)
if err != nil {
t.Fatal(err)
}
t.Logf("serverSig=%v", serverSig)
_, err = client.VerifySignature(serverMsg, serverSig, 0)
if err != nil {
t.Fatal(err)
}
t.Logf("client verified server signature")
}

177
vendor/github.com/alexbrainman/sspi/ntlm/http_test.go generated vendored Normal file

@ -0,0 +1,177 @@
// 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"
"github.com/alexbrainman/sspi/ntlm"
)
var (
testURL = flag.String("url", "", "server URL for TestNTLMHTTPClient")
)
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()
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))
}
}

265
vendor/github.com/alexbrainman/sspi/ntlm/ntlm.go generated vendored Normal file

@ -0,0 +1,265 @@
// 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"
"github.com/alexbrainman/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())
}
}
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)))
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 := sspi.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: sspi.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)
}
func updateContext(c *sspi.Context, dst, src []byte) (authCompleted bool, n int, err error) {
var inBuf, outBuf [1]sspi.SecBuffer
inBuf[0].Set(sspi.SECBUFFER_TOKEN, src)
inBufs := &sspi.SecBufferDesc{
Version: sspi.SECBUFFER_VERSION,
BuffersCount: 1,
Buffers: &inBuf[0],
}
outBuf[0].Set(sspi.SECBUFFER_TOKEN, dst)
outBufs := &sspi.SecBufferDesc{
Version: sspi.SECBUFFER_VERSION,
BuffersCount: 1,
Buffers: &outBuf[0],
}
ret := c.Update(nil, outBufs, inBufs)
switch ret {
case sspi.SEC_E_OK:
// session established -> return success
return true, int(outBuf[0].BufferSize), nil
case sspi.SEC_I_COMPLETE_NEEDED, sspi.SEC_I_COMPLETE_AND_CONTINUE:
ret = sspi.CompleteAuthToken(c.Handle, outBufs)
if ret != sspi.SEC_E_OK {
return false, 0, ret
}
case sspi.SEC_I_CONTINUE_NEEDED:
default:
return false, 0, ret
}
return false, int(outBuf[0].BufferSize), 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 := sspi.NewClientContext(cred, sspi.ISC_REQ_CONNECTION)
authCompleted, n, err := updateContext(c, negotiate, nil)
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 := updateContext(c.sctxt, 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
}
// Sizes queries the client context for the sizes used in per-message
// functions. It returns the maximum token size used in authentication
// exchanges, the maximum signature size, the preferred integral size of
// messages, the size of any security trailer, and any error.
func (c *ClientContext) Sizes() (uint32, uint32, uint32, uint32, error) {
return c.sctxt.Sizes()
}
// 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 := sspi.NewServerContext(cred, sspi.ASC_REQ_CONNECTION)
authCompleted, n, err := updateContext(c, 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 := updateContext(c.sctxt, 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()
}
// Sizes queries the server context for the sizes used in per-message
// functions. It returns the maximum token size used in authentication
// exchanges, the maximum signature size, the preferred integral size of
// messages, the size of any security trailer, and any error.
func (c *ServerContext) Sizes() (uint32, uint32, uint32, uint32, error) {
return c.sctxt.Sizes()
}

119
vendor/github.com/alexbrainman/sspi/ntlm/ntlm_test.go generated vendored Normal file

@ -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"
"github.com/alexbrainman/sspi"
"github.com/alexbrainman/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)
}

@ -0,0 +1,82 @@
// 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 schannel
import (
"syscall"
"unsafe"
"github.com/alexbrainman/sspi"
)
// TODO: maybe move all these into a separate package or something
func (c *Client) streamSizes() (*_SecPkgContext_StreamSizes, error) {
// TODO: do not retrive _SecPkgContext_StreamSizes every time (cache the data and invalidate it every time is possible can be changed: handshake, redo, ...)
// TODO: maybe return (header, trailer, maxmsg int, err error) instead
// TODO: maybe this needs to be exported
var ss _SecPkgContext_StreamSizes
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_STREAM_SIZES, (*byte)(unsafe.Pointer(&ss)))
if ret != sspi.SEC_E_OK {
return nil, ret
}
return &ss, nil
}
func (c *Client) ProtocolInfo() (name string, major, minor uint32, err error) {
var pi _SecPkgContext_ProtoInfo
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_PROTO_INFO, (*byte)(unsafe.Pointer(&pi)))
if ret != sspi.SEC_E_OK {
return "", 0, 0, ret
}
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(pi.ProtocolName)))
s := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(pi.ProtocolName))[:])
return s, pi.MajorVersion, pi.MinorVersion, nil
}
func (c *Client) UserName() (string, error) {
var ns _SecPkgContext_Names
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_NAMES, (*byte)(unsafe.Pointer(&ns)))
if ret != sspi.SEC_E_OK {
return "", ret
}
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ns.UserName)))
s := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(ns.UserName))[:])
return s, nil
}
func (c *Client) AuthorityName() (string, error) {
var a _SecPkgContext_Authority
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_AUTHORITY, (*byte)(unsafe.Pointer(&a)))
if ret != sspi.SEC_E_OK {
return "", ret
}
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(a.AuthorityName)))
s := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(a.AuthorityName))[:])
return s, nil
}
func (c *Client) KeyInfo() (sessionKeySize uint32, sigAlg uint32, sigAlgName string, encAlg uint32, encAlgName string, err error) {
var ki _SecPkgContext_KeyInfo
ret := sspi.QueryContextAttributes(c.ctx.Handle, _SECPKG_ATTR_KEY_INFO, (*byte)(unsafe.Pointer(&ki)))
if ret != sspi.SEC_E_OK {
return 0, 0, "", 0, "", ret
}
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ki.SignatureAlgorithmName)))
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ki.EncryptAlgorithmName)))
saname := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(ki.SignatureAlgorithmName))[:])
eaname := syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(ki.EncryptAlgorithmName))[:])
return ki.KeySize, ki.SignatureAlgorithm, saname, ki.EncryptAlgorithm, eaname, nil
}
// Sizes queries the context for the sizes used in per-message functions.
// It returns the maximum token size used in authentication exchanges, the
// maximum signature size, the preferred integral size of messages, the
// size of any security trailer, and any error.
func (c *Client) Sizes() (uint32, uint32, uint32, uint32, error) {
return c.ctx.Sizes()
}

78
vendor/github.com/alexbrainman/sspi/schannel/buffer.go generated vendored Normal file

@ -0,0 +1,78 @@
// 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 schannel
import (
"io"
"github.com/alexbrainman/sspi"
)
type inputBuffer struct {
data []byte
reader io.Reader
}
func newInputBuffer(initialsize int, reader io.Reader) *inputBuffer {
return &inputBuffer{
data: make([]byte, 0, initialsize),
reader: reader,
}
}
// copy copies data d into buffer ib. copy grows destination if needed.
func (ib *inputBuffer) copy(d []byte) int {
// TODO: check all call sites, maybe this can be made more efficient
return copy(ib.data, d)
}
func (ib *inputBuffer) reset() {
ib.data = ib.data[:0]
}
func (ib *inputBuffer) grow() {
b := make([]byte, len(ib.data), cap(ib.data)*2)
copy(b, ib.data)
ib.data = b
}
func (ib *inputBuffer) readMore() error {
if len(ib.data) == cap(ib.data) {
ib.grow()
}
n0 := len(ib.data)
ib.data = ib.data[:cap(ib.data)]
n, err := ib.reader.Read(ib.data[n0:])
if err != nil {
return err
}
ib.data = ib.data[:n0+n]
return nil
}
func (ib *inputBuffer) bytes() []byte {
return ib.data
}
func sendOutBuffer(w io.Writer, b *sspi.SecBuffer) error {
_, err := b.WriteAll(w)
// TODO: see if I can preallocate buffers instead
b.Free()
b.Set(sspi.SECBUFFER_TOKEN, nil)
return err
}
// indexOfSecBuffer searches buffers bs for buffer type buftype.
// It returns -1 if not found.
func indexOfSecBuffer(bs []sspi.SecBuffer, buftype uint32) int {
for i := range bs {
if bs[i].BufferType == buftype {
return i
}
}
return -1
}

276
vendor/github.com/alexbrainman/sspi/schannel/client.go generated vendored Normal file

@ -0,0 +1,276 @@
// 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 schannel
import (
"errors"
"io"
"syscall"
"unsafe"
"github.com/alexbrainman/sspi"
)
// TODO: add documentation
// TODO: maybe come up with a better name
type Client struct {
ctx *sspi.Context
conn io.ReadWriter
inbuf *inputBuffer
}
func NewClientContext(cred *sspi.Credentials, conn io.ReadWriter) *Client {
return &Client{
ctx: sspi.NewClientContext(cred, sspi.ISC_REQ_STREAM|sspi.ISC_REQ_ALLOCATE_MEMORY|sspi.ISC_REQ_EXTENDED_ERROR|sspi.ISC_REQ_MANUAL_CRED_VALIDATION),
conn: conn,
// TODO: decide how large this buffer needs to be (it cannot be too small otherwise messages won't fit)
inbuf: newInputBuffer(1000, conn),
}
}
func (c *Client) Handshake(serverName string) error {
name, err := syscall.UTF16PtrFromString(serverName)
if err != nil {
return err
}
inBuf := []sspi.SecBuffer{
{BufferType: sspi.SECBUFFER_TOKEN},
{BufferType: sspi.SECBUFFER_EMPTY},
}
// TODO: InitializeSecurityContext doco says that inBufs should be nil on the first call
inBufs := sspi.NewSecBufferDesc(inBuf[:])
outBuf := []sspi.SecBuffer{
{BufferType: sspi.SECBUFFER_TOKEN},
}
outBufs := sspi.NewSecBufferDesc(outBuf)
for {
ret := c.ctx.Update(name, outBufs, inBufs)
// send data to peer
err := sendOutBuffer(c.conn, &outBuf[0])
if err != nil {
return err
}
// update input buffer
fetchMore := true
switch ret {
case sspi.SEC_E_OK, sspi.SEC_I_CONTINUE_NEEDED:
if inBuf[1].BufferType == sspi.SECBUFFER_EXTRA {
c.inbuf.copy(inBuf[1].Bytes())
fetchMore = false
} else {
c.inbuf.reset()
}
}
// decide what to do next
switch ret {
case sspi.SEC_E_OK:
// negotiation is competed
return nil
case sspi.SEC_I_CONTINUE_NEEDED, sspi.SEC_E_INCOMPLETE_MESSAGE:
// continue on
default:
return ret
}
// fetch more input data if needed
if fetchMore {
err := c.inbuf.readMore()
if err != nil {
return err
}
}
inBuf[0].Set(sspi.SECBUFFER_TOKEN, c.inbuf.bytes())
inBuf[1].Set(sspi.SECBUFFER_EMPTY, nil)
}
}
// TODO: protect Handshake, Read, Write and Shutdown with locks
// TODO: call Handshake at the start Read and Write unless handshake is already complete
func (c *Client) writeBlock(data []byte) (int, error) {
ss, err := c.streamSizes()
if err != nil {
return 0, err
}
// TODO: maybe make this buffer (and header and trailer buffers) part of Context struct
var b [4]sspi.SecBuffer
b[0].Set(sspi.SECBUFFER_STREAM_HEADER, make([]byte, ss.Header))
b[1].Set(sspi.SECBUFFER_DATA, data)
b[2].Set(sspi.SECBUFFER_STREAM_TRAILER, make([]byte, ss.Trailer))
b[3].Set(sspi.SECBUFFER_EMPTY, nil)
ret := sspi.EncryptMessage(c.ctx.Handle, 0, sspi.NewSecBufferDesc(b[:]), 0)
switch ret {
case sspi.SEC_E_OK:
case sspi.SEC_E_CONTEXT_EXPIRED:
// TODO: handle this
panic("writeBlock: SEC_E_CONTEXT_EXPIRED")
default:
return 0, ret
}
n1, err := b[0].WriteAll(c.conn)
if err != nil {
return n1, err
}
n2, err := b[1].WriteAll(c.conn)
if err != nil {
return n1 + n2, err
}
n3, err := b[2].WriteAll(c.conn)
return n1 + n2 + n3, err
}
func (c *Client) Write(b []byte) (int, error) {
ss, err := c.streamSizes()
if err != nil {
return 0, err
}
// TODO: handle redoing context here
total := 0
for len(b) > 0 {
// TODO: maybe use ss.BlockSize to decide on optimum block size
b2 := b
if len(b) > int(ss.MaximumMessage) {
b2 = b2[:ss.MaximumMessage]
}
n, err := c.writeBlock(b2)
total += n
if err != nil {
return total, err
}
b = b[len(b2):]
}
return total, nil
}
func (c *Client) Read(data []byte) (int, error) {
if len(c.inbuf.bytes()) == 0 {
err := c.inbuf.readMore()
if err != nil {
return 0, err
}
}
var b [4]sspi.SecBuffer
desc := sspi.NewSecBufferDesc(b[:])
loop:
for {
b[0].Set(sspi.SECBUFFER_DATA, c.inbuf.bytes())
b[1].Set(sspi.SECBUFFER_EMPTY, nil)
b[2].Set(sspi.SECBUFFER_EMPTY, nil)
b[3].Set(sspi.SECBUFFER_EMPTY, nil)
ret := sspi.DecryptMessage(c.ctx.Handle, desc, 0, nil)
switch ret {
case sspi.SEC_E_OK:
break loop
case sspi.SEC_E_INCOMPLETE_MESSAGE:
// TODO: it seems b[0].BufferSize or b[1].BufferSize contains "how many more bytes needed for full message" - maybe use it somehow
// read more and try again
err := c.inbuf.readMore()
if err != nil {
return 0, err
}
default:
// TODO: handle other ret values
return 0, errors.New("not implemented")
}
}
i := indexOfSecBuffer(b[:], sspi.SECBUFFER_DATA)
if i == -1 {
return 0, errors.New("DecryptMessage did not return SECBUFFER_DATA")
}
n := copy(data, b[i].Bytes())
i = indexOfSecBuffer(b[:], sspi.SECBUFFER_EXTRA)
if i == -1 {
c.inbuf.reset()
} else {
c.inbuf.copy(b[i].Bytes())
}
return n, nil
}
func (c *Client) applyShutdownControlToken() error {
data := uint32(_SCHANNEL_SHUTDOWN)
b := sspi.SecBuffer{
BufferType: sspi.SECBUFFER_TOKEN,
Buffer: (*byte)(unsafe.Pointer(&data)),
BufferSize: uint32(unsafe.Sizeof(data)),
}
desc := sspi.SecBufferDesc{
Version: sspi.SECBUFFER_VERSION,
BuffersCount: 1,
Buffers: &b,
}
ret := sspi.ApplyControlToken(c.ctx.Handle, &desc)
if ret != sspi.SEC_E_OK {
return ret
}
return nil
}
func (c *Client) Shutdown() error {
err := c.applyShutdownControlToken()
if err != nil {
return err
}
inBuf := []sspi.SecBuffer{
{BufferType: sspi.SECBUFFER_TOKEN},
{BufferType: sspi.SECBUFFER_EMPTY},
}
inBufs := sspi.NewSecBufferDesc(inBuf[:])
outBuf := []sspi.SecBuffer{
{BufferType: sspi.SECBUFFER_TOKEN},
}
outBufs := sspi.NewSecBufferDesc(outBuf)
for {
// TODO: I am not sure if I can pass nil as targname
ret := c.ctx.Update(nil, outBufs, inBufs)
// send data to peer
err := sendOutBuffer(c.conn, &outBuf[0])
if err != nil {
return err
}
// update input buffer
fetchMore := true
switch ret {
case sspi.SEC_E_OK, sspi.SEC_I_CONTINUE_NEEDED:
if inBuf[1].BufferType == sspi.SECBUFFER_EXTRA {
c.inbuf.copy(inBuf[1].Bytes())
fetchMore = false
} else {
c.inbuf.reset()
}
}
// decide what to do next
switch ret {
case sspi.SEC_E_OK, sspi.SEC_E_CONTEXT_EXPIRED:
// shutdown is competed
return nil
case sspi.SEC_I_CONTINUE_NEEDED, sspi.SEC_E_INCOMPLETE_MESSAGE:
// continue on
default:
return ret
}
// fetch more input data if needed
if fetchMore {
err := c.inbuf.readMore()
if err != nil {
return err
}
}
inBuf[0].Set(sspi.SECBUFFER_TOKEN, c.inbuf.bytes())
inBuf[1].Set(sspi.SECBUFFER_EMPTY, nil)
}
}

47
vendor/github.com/alexbrainman/sspi/schannel/creds.go generated vendored Normal file

@ -0,0 +1,47 @@
// 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 schannel provides access to the Secure Channel SSP Package.
//
package schannel
import (
"unsafe"
"github.com/alexbrainman/sspi"
)
// TODO: add documentation
// PackageInfo contains Secure Channel SSP package description.
var PackageInfo *sspi.PackageInfo
func init() {
var err error
PackageInfo, err = sspi.QueryPackageInfo(sspi.UNISP_NAME)
if err != nil {
panic("failed to fetch Schannel package info: " + err.Error())
}
}
func acquireCredentials(creduse uint32) (*sspi.Credentials, error) {
sc := &__SCHANNEL_CRED{
Version: __SCHANNEL_CRED_VERSION,
// TODO: allow for Creds / CredCount
// TODO: allow for RootStore
// TODO: allow for EnabledProtocols
// TODO: allow for MinimumCipherStrength / MaximumCipherStrength
}
c, err := sspi.AcquireCredentials(sspi.UNISP_NAME, creduse, (*byte)(unsafe.Pointer(sc)))
if err != nil {
return nil, err
}
return c, nil
}
func AcquireClientCredentials() (*sspi.Credentials, error) {
return acquireCredentials(sspi.SECPKG_CRED_OUTBOUND)
}

@ -0,0 +1,77 @@
// 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 schannel_test
import (
"fmt"
"io/ioutil"
"net"
"testing"
"github.com/alexbrainman/sspi/schannel"
)
func TestPackageInfo(t *testing.T) {
want := "Microsoft Unified Security Protocol Provider"
if schannel.PackageInfo.Name != want {
t.Fatalf(`invalid Schannel package name of %q, %q is expected.`, schannel.PackageInfo.Name, want)
}
}
func TestSchannel(t *testing.T) {
cred, err := schannel.AcquireClientCredentials()
if err != nil {
t.Fatal(err)
}
defer cred.Release()
conn, err := net.Dial("tcp", "microsoft.com:https")
if err != nil {
t.Fatal(err)
}
defer conn.Close()
client := schannel.NewClientContext(cred, conn)
err = client.Handshake("microsoft.com")
if err != nil {
t.Fatal(err)
}
protoName, major, minor, err := client.ProtocolInfo()
if err != nil {
t.Fatal(err)
}
t.Logf("protocol info: %s %d.%d", protoName, major, minor)
userName, err := client.UserName()
if err != nil {
t.Fatal(err)
}
t.Logf("user name: %q", userName)
authorityName, err := client.AuthorityName()
if err != nil {
t.Fatal(err)
}
t.Logf("authority name: %q", authorityName)
sessionKeySize, sigAlg, sigAlgName, encAlg, encAlgName, err := client.KeyInfo()
if err != nil {
t.Fatal(err)
}
t.Logf("key info: session_key_size=%d signature_alg=%q(%d) encryption_alg=%q(%d)", sessionKeySize, sigAlgName, sigAlg, encAlgName, encAlg)
// TODO: add some code to verify if negotiated connection is suitable (ciper and so on)
_, err = fmt.Fprintf(client, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
if err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadAll(client)
if err != nil {
t.Fatal(err)
}
t.Logf("web page: %q", data)
err = client.Shutdown()
if err != nil {
t.Fatal(err)
}
}

109
vendor/github.com/alexbrainman/sspi/schannel/syscall.go generated vendored Normal file

@ -0,0 +1,109 @@
// 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 schannel
import (
"syscall"
)
// TODO: maybe put all these into a separate package, like sspi/schannel/winapi or similar
const (
__SCHANNEL_CRED_VERSION = 4
_SP_PROT_PCT1_SERVER = 0x00000001
_SP_PROT_PCT1_CLIENT = 0x00000002
_SP_PROT_PCT1 = _SP_PROT_PCT1_SERVER | _SP_PROT_PCT1_CLIENT
_SP_PROT_SSL2_SERVER = 0x00000004
_SP_PROT_SSL2_CLIENT = 0x00000008
_SP_PROT_SSL2 = _SP_PROT_SSL2_SERVER | _SP_PROT_SSL2_CLIENT
_SP_PROT_SSL3_SERVER = 0x00000010
_SP_PROT_SSL3_CLIENT = 0x00000020
_SP_PROT_SSL3 = _SP_PROT_SSL3_SERVER | _SP_PROT_SSL3_CLIENT
_SP_PROT_TLS1_SERVER = 0x00000040
_SP_PROT_TLS1_CLIENT = 0x00000080
_SP_PROT_TLS1 = _SP_PROT_TLS1_SERVER | _SP_PROT_TLS1_CLIENT
_SP_PROT_SSL3TLS1_CLIENTS = _SP_PROT_TLS1_CLIENT | _SP_PROT_SSL3_CLIENT
_SP_PROT_SSL3TLS1_SERVERS = _SP_PROT_TLS1_SERVER | _SP_PROT_SSL3_SERVER
_SP_PROT_SSL3TLS1 = _SP_PROT_SSL3 | _SP_PROT_TLS1
)
type __SCHANNEL_CRED struct {
Version uint32
CredCount uint32
Creds *syscall.CertContext
RootStore syscall.Handle // TODO: make sure this field is syscall.Handle
cMappers uint32
aphMappers uintptr
SupportedAlgCount uint32
SupportedAlgs *uint32
EnabledProtocols uint32
MinimumCipherStrength uint32
MaximumCipherStrength uint32
SessionLifespan uint32
Flags uint32
CredFormat uint32
}
const (
_SECPKG_ATTR_SIZES = 0
_SECPKG_ATTR_NAMES = 1
_SECPKG_ATTR_LIFESPAN = 2
_SECPKG_ATTR_DCE_INFO = 3
_SECPKG_ATTR_STREAM_SIZES = 4
_SECPKG_ATTR_KEY_INFO = 5
_SECPKG_ATTR_AUTHORITY = 6
_SECPKG_ATTR_PROTO_INFO = 7
_SECPKG_ATTR_PASSWORD_EXPIRY = 8
_SECPKG_ATTR_SESSION_KEY = 9
_SECPKG_ATTR_PACKAGE_INFO = 10
_SECPKG_ATTR_USER_FLAGS = 11
_SECPKG_ATTR_NEGOTIATION_INFO = 12
_SECPKG_ATTR_NATIVE_NAMES = 13
_SECPKG_ATTR_FLAGS = 14
_SCHANNEL_RENEGOTIATE = 0
_SCHANNEL_SHUTDOWN = 1
_SCHANNEL_ALERT = 2
)
type _SecPkgContext_StreamSizes struct {
Header uint32
Trailer uint32
MaximumMessage uint32
Buffers uint32
BlockSize uint32
}
type _SecPkgContext_ProtoInfo struct {
ProtocolName *uint16
MajorVersion uint32
MinorVersion uint32
}
type _SecPkgContext_Names struct {
UserName *uint16
}
type _SecPkgContext_Authority struct {
AuthorityName *uint16
}
type _SecPkgContext_KeyInfo struct {
SignatureAlgorithmName *uint16
EncryptAlgorithmName *uint16
KeySize uint32
SignatureAlgorithm uint32
EncryptAlgorithm uint32
}
// TODO: SecPkgContext_ConnectionInfo
// TODO: SECPKG_ATTR_REMOTE_CERT_CONTEXT
// TODO: SECPKG_ATTR_LOCAL_CERT_CONTEXT
// TODO: SecPkgContext_IssuerListInfoEx

177
vendor/github.com/alexbrainman/sspi/sspi.go generated vendored Normal file

@ -0,0 +1,177 @@
// 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, targname *uint16, 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 NewClientContext(cred *Credentials, flags uint32) *Context {
return &Context{
Cred: cred,
updFn: initialize,
RequestedFlags: flags,
}
}
func NewServerContext(cred *Credentials, flags uint32) *Context {
return &Context{
Cred: cred,
updFn: accept,
RequestedFlags: flags,
}
}
func initialize(c *Context, targname *uint16, h, newh *CtxtHandle, out, in *SecBufferDesc) syscall.Errno {
return InitializeSecurityContext(&c.Cred.Handle, h, targname, c.RequestedFlags,
0, SECURITY_NATIVE_DREP, in, 0, newh, out, &c.EstablishedFlags, &c.expiry)
}
func accept(c *Context, targname *uint16, 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(targname *uint16, out, in *SecBufferDesc) syscall.Errno {
h := c.Handle
if c.Handle == nil {
c.Handle = &c.handle
}
return c.updFn(c, targname, h, c.Handle, out, in)
}
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
}
// Sizes queries the context for the sizes used in per-message functions.
// It returns the maximum token size used in authentication exchanges, the
// maximum signature size, the preferred integral size of messages, the
// size of any security trailer, and any error.
func (c *Context) Sizes() (uint32, uint32, uint32, uint32, error) {
var s _SecPkgContext_Sizes
ret := QueryContextAttributes(c.Handle, _SECPKG_ATTR_SIZES, (*byte)(unsafe.Pointer(&s)))
if ret != SEC_E_OK {
return 0, 0, 0, 0, ret
}
return s.MaxToken, s.MaxSignature, s.BlockSize, s.SecurityTrailer, nil
}
// NewSecBufferDesc returns an initialized SecBufferDesc describing the
// provided SecBuffer.
func NewSecBufferDesc(b []SecBuffer) *SecBufferDesc {
return &SecBufferDesc{
Version: SECBUFFER_VERSION,
BuffersCount: uint32(len(b)),
Buffers: &b[0],
}
}

33
vendor/github.com/alexbrainman/sspi/sspi_test.go generated vendored Normal file

@ -0,0 +1,33 @@
// 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 (
"testing"
"github.com/alexbrainman/sspi"
)
func TestQueryPackageInfo(t *testing.T) {
pkgnames := []string{
sspi.NTLMSP_NAME,
sspi.MICROSOFT_KERBEROS_NAME,
sspi.NEGOSSP_NAME,
sspi.UNISP_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
}
}
}

174
vendor/github.com/alexbrainman/sspi/syscall.go generated vendored Normal file

@ -0,0 +1,174 @@
// 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"
)
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)
SEC_E_LOGON_DENIED = syscall.Errno(0x8009030c)
SEC_E_CONTEXT_EXPIRED = syscall.Errno(0x80090317) // not sure if the value is valid
SEC_E_INCOMPLETE_MESSAGE = syscall.Errno(0x80090318)
NTLMSP_NAME = "NTLM"
MICROSOFT_KERBEROS_NAME = "Kerberos"
NEGOSSP_NAME = "Negotiate"
UNISP_NAME = "Microsoft Unified Security Protocol Provider"
_SECPKG_ATTR_SIZES = 0
_SECPKG_ATTR_NAMES = 1
_SECPKG_ATTR_LIFESPAN = 2
_SECPKG_ATTR_DCE_INFO = 3
_SECPKG_ATTR_STREAM_SIZES = 4
_SECPKG_ATTR_KEY_INFO = 5
_SECPKG_ATTR_AUTHORITY = 6
_SECPKG_ATTR_PROTO_INFO = 7
_SECPKG_ATTR_PASSWORD_EXPIRY = 8
_SECPKG_ATTR_SESSION_KEY = 9
_SECPKG_ATTR_PACKAGE_INFO = 10
_SECPKG_ATTR_USER_FLAGS = 11
_SECPKG_ATTR_NEGOTIATION_INFO = 12
_SECPKG_ATTR_NATIVE_NAMES = 13
_SECPKG_ATTR_FLAGS = 14
)
type SecPkgInfo struct {
Capabilities uint32
Version uint16
RPCID uint16
MaxToken uint32
Name *uint16
Comment *uint16
}
type _SecPkgContext_Sizes struct {
MaxToken uint32
MaxSignature uint32
BlockSize uint32
SecurityTrailer uint32
}
//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)
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
}
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_DATA = 1
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
SECBUFFER_EMPTY = 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
//sys QueryContextAttributes(context *CtxtHandle, attribute uint32, buf *byte) (ret syscall.Errno) = secur32.QueryContextAttributesW
//sys EncryptMessage(context *CtxtHandle, qop uint32, message *SecBufferDesc, messageseqno uint32) (ret syscall.Errno) = secur32.EncryptMessage
//sys DecryptMessage(context *CtxtHandle, message *SecBufferDesc, messageseqno uint32, qop *uint32) (ret syscall.Errno) = secur32.DecryptMessage
//sys ApplyControlToken(context *CtxtHandle, input *SecBufferDesc) (ret syscall.Errno) = secur32.ApplyControlToken
//sys MakeSignature(context *CtxtHandle, qop uint32, message *SecBufferDesc, messageseqno uint32) (ret syscall.Errno) = secur32.MakeSignature
//sys VerifySignature(context *CtxtHandle, message *SecBufferDesc, messageseqno uint32, qop *uint32) (ret syscall.Errno) = secur32.VerifySignature

152
vendor/github.com/alexbrainman/sspi/zsyscall_windows.go generated vendored Normal file

@ -0,0 +1,152 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
package sspi
import (
"syscall"
"unsafe"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
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")
procQueryContextAttributesW = modsecur32.NewProc("QueryContextAttributesW")
procEncryptMessage = modsecur32.NewProc("EncryptMessage")
procDecryptMessage = modsecur32.NewProc("DecryptMessage")
procApplyControlToken = modsecur32.NewProc("ApplyControlToken")
procMakeSignature = modsecur32.NewProc("MakeSignature")
procVerifySignature = modsecur32.NewProc("VerifySignature")
)
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
}
func QueryContextAttributes(context *CtxtHandle, attribute uint32, buf *byte) (ret syscall.Errno) {
r0, _, _ := syscall.Syscall(procQueryContextAttributesW.Addr(), 3, uintptr(unsafe.Pointer(context)), uintptr(attribute), uintptr(unsafe.Pointer(buf)))
ret = syscall.Errno(r0)
return
}
func EncryptMessage(context *CtxtHandle, qop uint32, message *SecBufferDesc, messageseqno uint32) (ret syscall.Errno) {
r0, _, _ := syscall.Syscall6(procEncryptMessage.Addr(), 4, uintptr(unsafe.Pointer(context)), uintptr(qop), uintptr(unsafe.Pointer(message)), uintptr(messageseqno), 0, 0)
ret = syscall.Errno(r0)
return
}
func DecryptMessage(context *CtxtHandle, message *SecBufferDesc, messageseqno uint32, qop *uint32) (ret syscall.Errno) {
r0, _, _ := syscall.Syscall6(procDecryptMessage.Addr(), 4, uintptr(unsafe.Pointer(context)), uintptr(unsafe.Pointer(message)), uintptr(messageseqno), uintptr(unsafe.Pointer(qop)), 0, 0)
ret = syscall.Errno(r0)
return
}
func ApplyControlToken(context *CtxtHandle, input *SecBufferDesc) (ret syscall.Errno) {
r0, _, _ := syscall.Syscall(procApplyControlToken.Addr(), 2, uintptr(unsafe.Pointer(context)), uintptr(unsafe.Pointer(input)), 0)
ret = syscall.Errno(r0)
return
}
func MakeSignature(context *CtxtHandle, qop uint32, message *SecBufferDesc, messageseqno uint32) (ret syscall.Errno) {
r0, _, _ := syscall.Syscall6(procMakeSignature.Addr(), 4, uintptr(unsafe.Pointer(context)), uintptr(qop), uintptr(unsafe.Pointer(message)), uintptr(messageseqno), 0, 0)
ret = syscall.Errno(r0)
return
}
func VerifySignature(context *CtxtHandle, message *SecBufferDesc, messageseqno uint32, qop *uint32) (ret syscall.Errno) {
r0, _, _ := syscall.Syscall6(procVerifySignature.Addr(), 4, uintptr(unsafe.Pointer(context)), uintptr(unsafe.Pointer(message)), uintptr(messageseqno), uintptr(unsafe.Pointer(qop)), 0, 0)
ret = syscall.Errno(r0)
return
}