Skip to content

New SecContext interface #8

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ jobs:
echo pkgs=$(cd v3 && go list ./... | grep -v /examples/) >> "$GITHUB_ENV"

- name: Run tests
run: cd v3 && go test $pkgs -count 100 -coverprofile=../cover.out -covermode=atomic
run: pushd v3 ; go test -v $pkgs -count 100 -coverprofile=../cover.out -covermode=atomic; popd

- name: Check test coverage
uses: vladopajic/go-test-coverage@v2
uses: jake-scott/go-test-coverage@v1.0.0
with:
config: ./.testcoverage.yml
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ examples/go/gss-server-go
#
#
toolbin/

v3/cover.out
9 changes: 5 additions & 4 deletions .testcoverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@
# profiles into one. In this case, the profile should have a comma-separated list
# of profile files, e.g., 'cover_unit.out,cover_integration.out'.
profile: cover.out
working-directory: v3

# (optional; but recommended to set)
# When specified reported file paths will not contain local prefix in the output
local-prefix: "github.com/jake-scott/go-functional"
local-prefix: "github.com/golang-auth/go-gssapi/v3"

# Holds coverage thresholds percentages, values should be in range [0-100]
threshold:
# (optional; default 0)
# The minimum coverage that each file should have
file: 75
file: 60

# (optional; default 0)
# The minimum coverage that each package should have
package: 85
package: 70

# (optional; default 0)
# The minimum total coverage project should have
total: 95
total: 85

57 changes: 48 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
# GSSAPI interace for Go

Upto this point there have been several GSSAPI implementations for Go,
either native or C bindings. Developers needed to make a choice
of implementation because their interfaces were not unified. This
contrasts to the C language, where the bindings are specified
[in RFC 2744](https://datatracker.ietf.org/doc/html/rfc2744).
go-gssapi provides GSSAPI bindings for Go.

The interface specified in this package aims to fill that gap, albeit
without an RFC. The aim is to provide developers with a common, idomatic
programming interface, allowing users to switch out the actual implementation
depending on preference or local policy.
![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/golang-auth/go-gssapi)
[![Git Workflow](https://img.shields.io/github/actions/workflow/status/golang-auth/go-gssapi/checks.yml?branch=dev
)](https://img.shields.io/github/actions/workflow/status/golang-auth/go-gssapi/checks.yml?branch=de)
[![Go Version](https://img.shields.io/badge/go%20version-%3E=1.18-61CFDD.svg?style=flat-square)](https://golang.org/)
[![GO Reference](https://pkg.go.dev/badge/mod/github.com/golang-auth/go-gssapi)](https://pkg.go.dev/mod/github.com/golang-auth/go-gssapi/v3)

# Overview
This repository contains the Golang GSSAPI bindings interface and
provider-independent support functions [described in the wiki](https://github.com/golang-auth/go-gssapi/wiki/Golang-GSSAPI-bindings-specification). A GSSAPI
provider that implements the interface is required along with this package.

Versions prior to v3 of this repository contained a GSSAPI implementation that
used native Golang Kerberos and was not pluggable. As of version 3, the
providers are separate to the interface.

At this time, a provider that [wraps the C bindings](https://github.com/golang-auth/go-gssapi-c) is available. We feel that the native Go Kerberos implementation needs a reasonable amount of work for it to be production ready and so a native provider will come at a later stage. Developers are recommended to use the C wrappers
at this stage.

## Installation

Include the interface and common functions from this package:

```go
go get github.com/golang-auth/go-gssapi/v3
```

.. and a provider, for example `go-gssapi-c`:
```go
go get github.com/golang-auth/go-gssapi-c
```

## Getting started

The interface and provider packages should be included in the application. The
provider package does not need to supply any symbols to the app -- just loading
it is enough to have it register itself:

```go
package main

import (
_ "github.com/golang-auth/go-gssapi-c"
"github.com/golang-auth/go-gssapi/v3"
)

// GSSAPI-C is the name that go-gssapi-c registers itself under
var gss = gssapi.NewProvider("GSSAPI-C")
```

7 changes: 2 additions & 5 deletions examples/go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ go 1.22.4

replace github.com/golang-auth/go-gssapi/v3 => ../../v3

replace github.com/golang-auth/go-gssapi-c => ../../../go-gssapi-c
require github.com/golang-auth/go-gssapi/v3 v3.0.0-alpha

require (
github.com/golang-auth/go-gssapi-c v0.0.0-00010101000000-000000000000
github.com/golang-auth/go-gssapi/v3 v3.0.0-00010101000000-000000000000
)
require github.com/golang-auth/go-gssapi-c v0.0.0-20240828194135-955ba90d4511
16 changes: 10 additions & 6 deletions examples/go/go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-auth/go-gssapi-c v0.0.0-20240827133603-e7af9f04586a h1:qdMspd9EVKyHD4PqzYpCDpWaBwdm4oBY1u631biS/3U=
github.com/golang-auth/go-gssapi-c v0.0.0-20240827133603-e7af9f04586a/go.mod h1:7+YbBfLmM3gMF6DoCfjZFQBx1SXj1Uru6Y2tl77nhJ8=
github.com/golang-auth/go-gssapi-c v0.0.0-20240828194135-955ba90d4511 h1:k9cgAxS+AYKwAN7/moi03LK3EjTFUKeMRh9Cu2j4/D0=
github.com/golang-auth/go-gssapi-c v0.0.0-20240828194135-955ba90d4511/go.mod h1:rb9NLAgRMfr732Kvm1mOH5J6eIx/WULl8rAFNXSzGqY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Binary file removed examples/go/gss-client/gss-client
Binary file not shown.
4 changes: 2 additions & 2 deletions examples/go/gss-client/gss-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func main() {
}

// Wrap the message
outMsg, hasConf, err := secctx.Wrap(msgBuf, *confReq)
outMsg, hasConf, err := secctx.Wrap(msgBuf, *confReq, 0)
if err != nil {
log.Fatal(err)
}
Expand All @@ -168,7 +168,7 @@ func main() {
}
debug("Received MIC message (%d bytes):\n%s", len(msgMIC), formatToken(msgMIC))

if err = secctx.VerifyMIC(msgBuf, msgMIC); err != nil {
if _, err = secctx.VerifyMIC(msgBuf, msgMIC); err != nil {
log.Fatal(err)
}

Expand Down
4 changes: 2 additions & 2 deletions examples/go/gss-server/gss-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func handleConn(conn net.Conn) error {
}
debug("Received wrap message (%d bytes):\n%s", len(inMsg), formatToken(inMsg))

origMsg, conf, err := secctx.Unwrap(inMsg)
origMsg, conf, _, err := secctx.Unwrap(inMsg)
if err != nil {
return showErr(err)
}
Expand All @@ -174,7 +174,7 @@ func handleConn(conn net.Conn) error {
fmt.Printf(`Received %s message: "%s"`+"\n", protStr, origMsg)

// generate a MIC token to send back
if outToken, err = secctx.GetMIC(origMsg); err != nil {
if outToken, err = secctx.GetMIC(origMsg, 0); err != nil {
return showErr(err)
}

Expand Down
2 changes: 1 addition & 1 deletion v3/mechs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestMechString(t *testing.T) {
assert.Equal("GSS_MECH_SPNEGO", oid)

badMech := gssMechImpl(100)
assert.PanicsWithValue(ErrBadMech, func() { badMech.OidString() })
assert.PanicsWithValue(ErrBadMech, func() { badMech.String() })
}

func TestMechFromOid(t *testing.T) {
Expand Down
15 changes: 5 additions & 10 deletions v3/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,10 @@ type Provider interface {
// name: The GSSAPI Internal Name of the target.
// opts: Optional context establishment parameters, see [InitSecContextOption].
// Returns:
// A GSSAPI security context and a token to send to the acceptor for use with
// GSS_Accept_sec_context. The returned context may or may not be fully established.
//
// If [SecContext.ContinueNeeded()] returns true, additional message exchanges
// with the acceptor are required to fully establish the security context.
//
// A partially established context may allow the creation of protected messages.
// Check the [SecContextInfo.ProtectionReady] flag by calling [SecContext.Inquire()].
InitSecContext(name GssName, opts ...InitSecContextOption) (SecContext, []byte, error) // RFC 2743 § 2.2.1
// A uninitialized GSSAPI security context ready for exchanging tokens with the peer when
// the first call to [Continue()] with an empty input token is made. [ContinueNeeded()] will true
// when this call returns successfully.
InitSecContext(name GssName, opts ...InitSecContextOption) (SecContext, error) // RFC 2743 § 2.2.1

// AcceptSecContext corresponds to the GSS_Accept_sec_context function from RFC 2743 § 2.2.2.
// Parameters:
Expand All @@ -125,7 +120,7 @@ type Provider interface {
//
// A partially established context may allow the creation of protected messages.
// Check the [SecContextInfo.ProtectionReady] flag by calling [SecContext.Inquire()].
AcceptSecContext(cred Credential, inputToken []byte) (SecContext, []byte, error) // RFC 2743 § 2.2.2
AcceptSecContext(cred Credential) (SecContext, error) // RFC 2743 § 2.2.2

// ImportSecContext corresponds to the GSS_Import_sec_context function from RFC 2743 § 2.2.9
// Parameters:
Expand Down
105 changes: 105 additions & 0 deletions v3/provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: Apache-2.0
package gssapi

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

type someProvider struct {
name string
}

func (someProvider) ImportName(name string, nameType GssNameType) (GssName, error) {
return nil, nil
}

func (someProvider) AcquireCredential(name GssName, mechs []GssMech, usage CredUsage, lifetime time.Duration) (Credential, error) {
return nil, nil
}

func (someProvider) InitSecContext(name GssName, opts ...InitSecContextOption) (SecContext, error) {
return nil, nil
}

func (someProvider) AcceptSecContext(cred Credential) (SecContext, error) {
return nil, nil
}

func (someProvider) ImportSecContext(b []byte) (SecContext, error) {
return nil, nil
}

func (someProvider) InquireNamesForMech(m GssMech) ([]GssNameType, error) {
return nil, nil
}

func TestRegister(t *testing.T) {
assert := assert.New(t)

registry.libs = make(map[string]ProviderFactory)

assert.Equal(0, len(registry.libs))

factory := func() Provider {
return someProvider{name: "TEST"}
}

RegisterProvider("test", factory)
assert.Equal(1, len(registry.libs))
f, ok := registry.libs["test"]
assert.True(ok)
assert.NotNil(f)

p := NewProvider("test")
assert.NotNil(p)
sp, ok := p.(someProvider)
assert.True(ok)
assert.Equal("TEST", sp.name)

assert.Panics(func() { NewProvider("") })
assert.Panics(func() { NewProvider("xyz") })
}

type someCredential struct{}

func (someCredential) Release() error {
return nil
}

func (someCredential) Inquire() (*CredInfo, error) {
return nil, nil
}

func (someCredential) Add(name GssName, mech GssMech, usage CredUsage, initiatorLifetime time.Duration, acceptorLifetime time.Duration) error {
return nil
}

func (someCredential) InquireByMech(mech GssMech) (*CredInfo, error) {
return nil, nil
}

func TestInitSecContextOptions(t *testing.T) {
assert := assert.New(t)

cred := &someCredential{}

opts := []InitSecContextOption{
WithInitiatorCredential(cred),
WithInitatorMech(GSS_MECH_KRB5),
WithInitiatorFlags(ContextFlagMutual | ContextFlagInteg),
WithInitiatorLifetime(time.Second * 123),
}

isco := InitSecContextOptions{}
for _, f := range opts {
f(&isco)
}

assert.EqualValues(cred, isco.Credential)
assert.EqualValues(GSS_MECH_KRB5, isco.Mech)
assert.Equal(ContextFlagMutual|ContextFlagInteg, isco.Flags)
assert.Equal(time.Second*123, isco.Lifetime)
}
6 changes: 3 additions & 3 deletions v3/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const (
errDefectiveToken
errDefectiveCredential
errCredentialsExpired
errContexctExpired
errContextExpired
errFailure
errBadQop
errUnauthorized
Expand Down Expand Up @@ -71,7 +71,7 @@ var ErrBadQop = errors.New("the quality-of-protection (QOP) requested could not
var ErrUnauthorized = errors.New("the operation is forbidden by local security policy")
var ErrUnavailable = errors.New("the operation or option is not available or supported")
var ErrDuplicateElement = errors.New("the requested credential element already exists")
var ErrNameNotMn = errors.New("the provided name was not mechsnism specific (MN)")
var ErrNameNotMn = errors.New("the provided name was not mechanism specific (MN)")

//lint:ignore ST1012 these aren't actually errors
var InfoContinueNeeded = errors.New("the routine must be called again to complete its function")
Expand Down Expand Up @@ -114,7 +114,7 @@ func (s FatalStatus) Fatal() error {
return ErrDefectiveCredential
case errCredentialsExpired:
return ErrCredentialsExpired
case errContexctExpired:
case errContextExpired:
return ErrContextExpired
case errFailure:
return ErrFailure
Expand Down
35 changes: 35 additions & 0 deletions v3/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,40 @@ func TestConstValues(t *testing.T) {
assert.Equal(InformationCode(16), infoGapToken)
}

func TestFatal(t *testing.T) {
assert := assert.New(t)

tests := []struct {
code FatalErrorCode
errContains string
}{
{errBadMech, "unsupported mech"},
{errBadName, "invalid name"},
{errBadNameType, "unsupported type"},
{errBadBindings, "channel bindings"},
{errBadStatus, "invalid status"},
{errBadMic, "invalid signature"},
{errNoCred, "no credentials"},
{errNoContext, "no context"},
{errDefectiveToken, "invalid token"},
{errDefectiveCredential, "invalid credential"},
{errCredentialsExpired, "credentials have expired"},
{errContextExpired, "context has expired"},
{errFailure, "unspecified GSS"},
{errBadQop, "quality-of-protection"},
{errUnauthorized, "operation is forbidden"},
{errUnavailable, "not available"},
{errDuplicateElement, "already exists"},
{errNameNotMn, "not mechanism"},
{1000, "invalid status"},
}

for _, tt := range tests {
fs := FatalStatus{FatalErrorCode: tt.code}
assert.Contains(fs.Fatal().Error(), tt.errContains)
}
}

func TestFatalUnwrap(t *testing.T) {
assert := assert.New(t)

Expand All @@ -49,6 +83,7 @@ func TestFatalError(t *testing.T) {
FatalErrorCode: errBadMech,
InfoStatus: InfoStatus{
InformationCode: infoDuplicateToken | infoGapToken,
MechErrors: []error{errors.New("TEST")},
},
}

Expand Down
Loading