feat: ssp: accept security context for krb5/ntlm/spnego (#111)

* feat: ssp: accept security context for krb5/ntlm/spnego

* fix tests

* fix ntlm signature

* ssp: capabilities: override only when empty
This commit is contained in:
oiweiwei
2026-04-20 10:35:17 +02:00
committed by GitHub
parent c28ed9e2ea
commit cfa498a548
32 changed files with 1818 additions and 131 deletions
+8 -1
View File
@@ -45,7 +45,7 @@ Examples rely on following environment variables:
| ---- | ----------- | ------- |
| **USERNAME** | The Domain\Username | `"MSAD2.COM\User"` |
| **PASSWORD** | The password | `"password"` |
| **PASSWORD_MD4** | The password hash (use [go run examples/helpers/nt_hash.go -d $PASSWORD](./examples/helpers/nt_hash.go) to generate the hash) | `"f077ca4b7d73486a45e75dcdd74cd5bd"` |
| **PASSWORD_MD4** | The password hash (use [go run examples/helpers/nthash/nt_hash.go -d $PASSWORD](./examples/helpers/nthash/nt_hash.go) to generate the hash) | `"f077ca4b7d73486a45e75dcdd74cd5bd"` |
| **WORKSTATION** | The workstation name | `"Ubuntu"` |
| **SERVER** | The server FQDN or IP | `"192.168.0.22"` |
| **SERVER_NAME** | The server NetBIOS name | `"WIN2019"` |
@@ -273,6 +273,13 @@ The set of changes includes:
| [MS-WMI](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmi) | Windows Management Instrumentation Remote Protocol | [github.com/oiweiwei/msrpc/wmi](./msrpc/dcom/wmi) |
| [MS-WSRM](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wsrm) | Windows System Resource Manager (WSRM) Protocol | [github.com/oiweiwei/msrpc/wsrm](./msrpc/dcom/wsrm) |
### Other Stubs
| Code | Description | Package |
| ------- | -------- | ------- |
| [MIMICOM](https://gist.github.com/gentilkiwi/e3d9c92b93ed4bb48f7956492c1d335a) | Mimikatz COM Interface | [github.com/oiweiwei/msrpc/mimicom](./msrpc/mimicom) |
### Documentation
The codegen package also generates the documentation for the generated code
+11 -1
View File
@@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
@@ -92,7 +93,16 @@ func FileStore(p string, f *File) {
}
func FindPath() []string {
return strings.Split(os.Getenv("MSIDLPATH"), ":")
var paths []string
_, p, _, ok := runtime.Caller(0)
if ok {
paths = append(paths, filepath.Join(filepath.Dir(p), "idl"))
}
if p := os.Getenv("MSIDLPATH"); p != "" {
paths = append(paths, strings.Split(p, ":")...)
}
return paths
}
func LookupType(n string) *Type {
+205
View File
@@ -0,0 +1,205 @@
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: guiddef.h
//
// Contents: GUID definition
//
//----------------------------------------------------------------------------
//#ifndef GUID_DEFINED
//#define GUID_DEFINED
//#if defined(__midl)
//typedef struct {
// unsigned long Data1;
// unsigned short Data2;
// unsigned short Data3;
// byte Data4[ 8 ];
//} GUID;
//#else
typedef struct _GUID {
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[ 8 ];
} GUID;
//#endif
//#endif
//
//#ifndef FAR
//#ifdef _WIN32
//#define FAR
//#else
//#define FAR _far
//#endif
//#endif
//
//#ifndef DECLSPEC_SELECTANY
//#if (_MSC_VER >= 1100)
//#define DECLSPEC_SELECTANY __declspec(selectany)
//#else
//#define DECLSPEC_SELECTANY
//#endif
//#endif
//
//#ifndef EXTERN_C
//#ifdef __cplusplus
//#define EXTERN_C extern "C"
//#else
//#define EXTERN_C extern
//#endif
//#endif
//
//#ifdef DEFINE_GUID
//#undef DEFINE_GUID
//#endif
//
//#ifdef INITGUID
//#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
// EXTERN_C const GUID DECLSPEC_SELECTANY name \
// = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
//#else
//#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
// EXTERN_C const GUID FAR name
//#endif // INITGUID
//
//#define DEFINE_OLEGUID(name, l, w1, w2) DEFINE_GUID(name, l, w1, w2, 0xC0,0,0,0,0,0,0,0x46)
//
//#ifndef _GUIDDEF_H_
//#define _GUIDDEF_H_
//
//#ifndef __LPGUID_DEFINED__
//#define __LPGUID_DEFINED__
typedef GUID *LPGUID;
//#endif
//
//#ifndef __LPCGUID_DEFINED__
//#define __LPCGUID_DEFINED__
typedef const GUID *LPCGUID;
//#endif
//
//#ifndef __IID_DEFINED__
//#define __IID_DEFINED__
typedef GUID IID;
typedef IID *LPIID;
//#define IID_NULL GUID_NULL
//#define IsEqualIID(riid1, riid2) IsEqualGUID(riid1, riid2)
typedef GUID CLSID;
typedef CLSID *LPCLSID;
//#define CLSID_NULL GUID_NULL
//#define IsEqualCLSID(rclsid1, rclsid2) IsEqualGUID(rclsid1, rclsid2)
typedef GUID FMTID;
typedef FMTID *LPFMTID;
//#define FMTID_NULL GUID_NULL
//#define IsEqualFMTID(rfmtid1, rfmtid2) IsEqualGUID(rfmtid1, rfmtid2)
//
//#ifdef __midl_proxy
//#define __MIDL_CONST
//#else
//#define __MIDL_CONST const
//#endif
//
//#ifndef _REFGUID_DEFINED
//#define _REFGUID_DEFINED
//#ifdef __cplusplus
//#define REFGUID const GUID &
//#else
//#define REFGUID const GUID * __MIDL_CONST
//#endif
//#endif
//
//#ifndef _REFIID_DEFINED
//#define _REFIID_DEFINED
//#ifdef __cplusplus
//#define REFIID const IID &
//#else
//#define REFIID const IID * __MIDL_CONST
//#endif
//#endif
//
//#ifndef _REFCLSID_DEFINED
//#define _REFCLSID_DEFINED
//#ifdef __cplusplus
//#define REFCLSID const IID &
//#else
//#define REFCLSID const IID * __MIDL_CONST
//#endif
//#endif
//
//#ifndef _REFFMTID_DEFINED
//#define _REFFMTID_DEFINED
//#ifdef __cplusplus
//#define REFFMTID const IID &
//#else
//#define REFFMTID const IID * __MIDL_CONST
//#endif
//#endif
//
//#endif // !__IID_DEFINED__
//
//#if !defined (__midl)
//#if !defined (_SYS_GUID_OPERATORS_)
//#define _SYS_GUID_OPERATORS_
//#include <string.h>
//
//// Faster (but makes code fatter) inline version...use sparingly
//#ifdef __cplusplus
//__inline int InlineIsEqualGUID(REFGUID rguid1, REFGUID rguid2)
//{
// return (
// ((unsigned long *) &rguid1)[0] == ((unsigned long *) &rguid2)[0] &&
// ((unsigned long *) &rguid1)[1] == ((unsigned long *) &rguid2)[1] &&
// ((unsigned long *) &rguid1)[2] == ((unsigned long *) &rguid2)[2] &&
// ((unsigned long *) &rguid1)[3] == ((unsigned long *) &rguid2)[3]);
//}
//
//__inline int IsEqualGUID(REFGUID rguid1, REFGUID rguid2)
//{
// return !memcmp(&rguid1, &rguid2, sizeof(GUID));
//}
//
//#else // ! __cplusplus
//
//#define InlineIsEqualGUID(rguid1, rguid2) \
// (((unsigned long *) rguid1)[0] == ((unsigned long *) rguid2)[0] && \
// ((unsigned long *) rguid1)[1] == ((unsigned long *) rguid2)[1] && \
// ((unsigned long *) rguid1)[2] == ((unsigned long *) rguid2)[2] && \
// ((unsigned long *) rguid1)[3] == ((unsigned long *) rguid2)[3])
//
//#define IsEqualGUID(rguid1, rguid2) (!memcmp(rguid1, rguid2, sizeof(GUID)))
//
//#endif // __cplusplus
//
//#ifdef __INLINE_ISEQUAL_GUID
//#undef IsEqualGUID
//#define IsEqualGUID(rguid1, rguid2) InlineIsEqualGUID(rguid1, rguid2)
//#endif
//
//// Same type, different name
//
//#define IsEqualIID(riid1, riid2) IsEqualGUID(riid1, riid2)
//#define IsEqualCLSID(rclsid1, rclsid2) IsEqualGUID(rclsid1, rclsid2)
//
//
//#if !defined _SYS_GUID_OPERATOR_EQ_ && !defined _NO_SYS_GUID_OPERATOR_EQ_
//#define _SYS_GUID_OPERATOR_EQ_
//// A couple of C++ helpers
//
//#ifdef __cplusplus
//__inline bool operator==(REFGUID guidOne, REFGUID guidOther)
//{
// return !!IsEqualGUID(guidOne,guidOther);
//}
//
//__inline bool operator!=(REFGUID guidOne, REFGUID guidOther)
//{
// return !(guidOne == guidOther);
//}
//#endif
//#endif // _SYS_GUID_OPERATOR_EQ_
//#endif // _SYS_GUID_OPERATORS_
//#endif // __midl
//#endif // _GUIDDEF_H_
+1 -1
View File
@@ -716,7 +716,7 @@ func (t *ExprTree) Expression(opts ...any) string {
switch t.Op {
case TERNARY:
if t.Cond.Op == UMUL || t.Cond.Op == IDENT {
ret = fmt.Sprintf(t.Lval.Expression(opts...))
ret = fmt.Sprint(t.Lval.Expression(opts...))
} else {
ret = fmt.Sprintf("(%s?%s:%s)", t.Cond.Expression(opts...), t.Lval.Expression(opts...), t.Rval.Expression(opts...))
}
+20
View File
@@ -62,11 +62,31 @@ func DomainName(un string) string {
return dn
}
func IsAnonymous(cred Credential) bool {
if cred == nil {
return true
}
p, ok := (any)(cred).(*passwordCred)
if !ok {
return false
}
return p.allowEmpty && p.userName == ""
}
// Anonymous function returns the anonymous password credentials.
func Anonymous() Password {
return &passwordCred{allowEmpty: true}
}
func New(un string, opts ...Option) Credential {
dn, un, wkst := parseDomainUserWorkstation(un, opts...)
return &userCred{
userName: un,
domainName: dn,
workstation: wkst,
}
}
func NewFromString(s string, opts ...Option) Password {
ss := strings.Split(s, "%")
if len(ss) > 1 {
+52
View File
@@ -0,0 +1,52 @@
package credential
import (
"context"
"strings"
)
// Database is an interface for storing and retrieving credentials.
type Database interface {
// Add adds a credential to the database.
Add(Credential)
// Lookup looks up a credential in the database.
Lookup(context.Context, Credential) (Credential, bool)
}
type localDatabase struct {
credentials map[string]Credential
}
// NewLocalDatabase creates a new in-memory database for credentials.
func NewLocalDatabase() Database {
return &localDatabase{
credentials: make(map[string]Credential),
}
}
// Add adds a credential to the local database.
func (db *localDatabase) Add(cred Credential) {
if cred != nil {
db.credentials[strings.ToLower(cred.UserName())] = cred
if cred.DomainName() != "" {
db.credentials[strings.ToLower(cred.DomainName()+"\\"+cred.UserName())] = cred
}
}
}
// Lookup looks up a credential in the local database.
func (db *localDatabase) Lookup(ctx context.Context, cred Credential) (Credential, bool) {
if cred == nil {
return nil, false
}
if cred.DomainName() != "" {
name := strings.ToLower(cred.DomainName() + "\\" + cred.UserName())
if cred, ok := db.credentials[name]; ok {
return cred, true
}
}
if cred, ok := db.credentials[strings.ToLower(cred.UserName())]; ok {
return cred, true
}
return nil, false
}
+134 -22
View File
@@ -3,6 +3,8 @@ package gssapi
import (
"context"
"fmt"
"github.com/oiweiwei/go-msrpc/ssp/name"
)
type ChannelBindings interface {
@@ -46,6 +48,13 @@ type SecurityContext struct {
MechanismStore *MechanismStore
// Local context credentials storage.
CredentialStore *CredentialStore
// The credential database for the server to
// validate incoming client credentials.
CredentialDatabase CredentialDatabase
// The list of acceptable server names.
ServerNames []*name.Name
// DefaultOptions for the context.
DefaultOptions []Option
}
// The security context key.
@@ -100,8 +109,9 @@ func WithMechanismFactory(value MechanismFactory, defaultConfig ...MechanismConf
func NewSecurityContext(ctx context.Context, opts ...ContextOption) context.Context {
var (
creds *CredentialStore
mechs *MechanismStore
creds *CredentialStore
mechs *MechanismStore
defaultOpts []Option
)
// fill-in the credentials and mechanisms local to the current
@@ -118,10 +128,16 @@ func NewSecurityContext(ctx context.Context, opts ...ContextOption) context.Cont
mechs = new(MechanismStore)
}
mechs.AddMechanism(o)
case Option:
defaultOpts = append(defaultOpts, o)
}
}
return context.WithValue(ctx, ctxKey{}, &SecurityContext{MechanismStore: mechs, CredentialStore: creds})
return context.WithValue(ctx, ctxKey{}, &SecurityContext{
MechanismStore: mechs,
CredentialStore: creds,
DefaultOptions: defaultOpts,
})
}
// ResetSecurityContext to it's initial state.
@@ -130,7 +146,11 @@ func ResetSecurityContext(ctx context.Context) context.Context {
if cc == nil {
return ctx
}
return context.WithValue(ctx, ctxKey{}, &SecurityContext{MechanismStore: cc.MechanismStore, CredentialStore: cc.CredentialStore})
return context.WithValue(ctx, ctxKey{}, &SecurityContext{
MechanismStore: cc.MechanismStore,
CredentialStore: cc.CredentialStore,
DefaultOptions: cc.DefaultOptions,
})
}
// FromContext retrieves the Security Context.
@@ -143,6 +163,7 @@ func FromContext(ctx context.Context) SecurityContext {
c.MechanismConfigs = nil // guard from changing the state.
c.Attributes = nil // guard from changing the state.
c.MechanismStore, c.CredentialStore = nil, nil // guard from changing the state.
c.DefaultOptions = nil
return c
}
return SecurityContext{}
@@ -201,7 +222,7 @@ func InitSecurityContext(ctx context.Context, tok *Token, opts ...Option) (*Toke
// initalize context with parameters.
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
cc.Compatibility = cfg.Compatibility
cc.QoP = cfg.QoP
@@ -257,6 +278,81 @@ func InitSecurityContext(ctx context.Context, tok *Token, opts ...Option) (*Toke
return &Token{}, nil
}
// Accept inbound security context.
func AcceptSecurityContext(ctx context.Context, tok *Token, opts ...Option) (*Token, error) {
cc := fromContext(ctx)
if cc == nil {
return nil, ErrNoContext
}
var err error
if cc.Status == NoContext {
// initalize context with parameters.
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
cc.Compatibility = cfg.Compatibility
cc.QoP = cfg.QoP
cc.Capabilities = cfg.Capabilities
cc.ContextTTL = cfg.ContextTTL
cc.TargetName = cfg.TargetName
cc.MechanismConfigs = cfg.MechanismConfigs
cc.CredentialDatabase = cfg.CredentialDatabase
cc.ServerNames = cfg.ServerNames
f := GetMechanism(ctx, cfg.MechanismType)
if f == nil {
return nil, ContextError(ctx, BadMech, ErrBadMech)
}
// get stored credentials.
cc.Credential = GetCredential(ctx, cfg.TargetName, f.Type(), InitiateOnly)
// optionally validate credential.
if cc.Credential != nil {
if validator, ok := (any)(cc.Credential.Value()).(interface{ Validate() error }); ok {
if err = validator.Validate(); err != nil {
return nil, ContextError(ctx, Failure, err)
}
}
}
// initiator is a server.
cc.IsServer = true
if cc.Mechanism, err = f.New(ctx); err != nil {
return nil, ContextError(ctx, Failure, err)
}
}
// handle error.
if tok, err = cc.Mechanism.Accept(ctx, tok); err != nil {
// set proper error if not set.
if cc.Status == NoContext || cc.Error == nil {
return tok, withContextStatus(ctx, Failure, err)
}
return tok, err
}
if caps := cc.Mechanism.Capabilities(ctx); caps != 0 {
cc.Capabilities = caps
}
// set proper status. (if nil is returned.)
if cc.Status == NoContext {
cc.Status = Complete
}
if tok != nil {
return tok, nil
}
return &Token{}, nil
}
// The maximum message size for the given limit.
func WrapSizeLimit(ctx context.Context, sz int, opts ...Option) int {
@@ -269,7 +365,7 @@ func WrapSizeLimit(ctx context.Context, sz int, opts ...Option) int {
return sz
}
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
return cc.Mechanism.WrapSizeLimit(ctx, sz, cfg.Capabilities.IsSet(Confidentiality))
}
@@ -285,13 +381,15 @@ func Wrap(ctx context.Context, tok *MessageToken, opts ...Option) (*MessageToken
return nil, ErrUnavailable
}
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
if cfg.QoP >= 0 {
tok.QoP = cfg.QoP
}
tok.Capabilities |= cfg.Capabilities
if tok.Capabilities == 0 {
tok.Capabilities |= cfg.Capabilities
}
return cc.Mechanism.Wrap(ctx, tok)
}
@@ -309,14 +407,16 @@ func WrapEx(ctx context.Context, tokEx *MessageTokenEx, opts ...Option) (*Messag
return nil, ErrUnavailable
}
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
if cfg.QoP >= 0 {
tokEx.QoP = cfg.QoP
}
for _, tok := range tokEx.Payloads {
tok.Capabilities |= cfg.Capabilities
if tok.Capabilities == 0 {
tok.Capabilities |= cfg.Capabilities
}
}
mechEx, ok := (interface{})(cc.Mechanism).(MechanismEx)
@@ -340,13 +440,15 @@ func Unwrap(ctx context.Context, tok *MessageToken, opts ...Option) (*MessageTok
return nil, ErrUnavailable
}
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
if cfg.QoP >= 0 {
tok.QoP = cfg.QoP
}
tok.Capabilities |= cfg.Capabilities
if tok.Capabilities == 0 {
tok.Capabilities |= cfg.Capabilities
}
return cc.Mechanism.Unwrap(ctx, tok)
}
@@ -364,14 +466,16 @@ func UnwrapEx(ctx context.Context, tokEx *MessageTokenEx, opts ...Option) (*Mess
return nil, ErrUnavailable
}
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
if cfg.QoP >= 0 {
tokEx.QoP = cfg.QoP
}
for _, tok := range tokEx.Payloads {
tok.Capabilities |= cfg.Capabilities
if tok.Capabilities == 0 {
tok.Capabilities |= cfg.Capabilities
}
}
mechEx, ok := (interface{})(cc.Mechanism).(MechanismEx)
@@ -395,13 +499,15 @@ func MakeSignature(ctx context.Context, tok *MessageToken, opts ...Option) (*Mes
return nil, ErrUnavailable
}
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
if cfg.QoP >= 0 {
tok.QoP = cfg.QoP
}
tok.Capabilities |= cfg.Capabilities
if tok.Capabilities == 0 {
tok.Capabilities |= cfg.Capabilities
}
return cc.Mechanism.MakeSignature(ctx, tok)
}
@@ -419,14 +525,16 @@ func MakeSignatureEx(ctx context.Context, tokEx *MessageTokenEx, opts ...Option)
return nil, ErrUnavailable
}
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
if cfg.QoP >= 0 {
tokEx.QoP = cfg.QoP
}
for _, tok := range tokEx.Payloads {
tok.Capabilities |= cfg.Capabilities
if tok.Capabilities == 0 {
tok.Capabilities |= cfg.Capabilities
}
}
mechEx, ok := (interface{})(cc.Mechanism).(MechanismEx)
@@ -450,13 +558,15 @@ func VerifySignature(ctx context.Context, tok *MessageToken, opts ...Option) err
return ErrUnavailable
}
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
if cfg.QoP >= 0 {
tok.QoP = cfg.QoP
}
tok.Capabilities |= cfg.Capabilities
if tok.Capabilities == 0 {
tok.Capabilities |= cfg.Capabilities
}
return cc.Mechanism.VerifySignature(ctx, tok)
}
@@ -472,14 +582,16 @@ func VerifySignatureEx(ctx context.Context, tokEx *MessageTokenEx, opts ...Optio
return ErrUnavailable
}
cfg := MakeOptions(opts...)
cfg := MakeOptions(append(cc.DefaultOptions, opts...)...)
if cfg.QoP >= 0 {
tokEx.QoP = cfg.QoP
}
for _, tok := range tokEx.Payloads {
tok.Capabilities |= cfg.Capabilities
if tok.Capabilities == 0 {
tok.Capabilities |= cfg.Capabilities
}
}
mechEx, ok := (interface{})(cc.Mechanism).(MechanismEx)
+56
View File
@@ -148,3 +148,59 @@ func GetCredentialValue(ctx context.Context, name string, mechanismType OID, usa
}
return nil
}
type CredentialDatabase interface {
// Value returns the actual database.
Value() any
// AllowGuest returns true if the database allows guest access.
AllowGuest() bool
// AllowAnonymous returns true if the database allows anonymous access.
AllowAnonymous() bool
}
type credentialDatabase struct {
value any
allowGuest bool
allowAnonymous bool
}
// Value returns the actual database value.
func (c *credentialDatabase) Value() any {
return c.value
}
// AllowGuest returns true if the database allows guest access.
func (c *credentialDatabase) AllowGuest() bool {
return c.allowGuest
}
// AllowAnonymous returns true if the database allows anonymous access.
func (c *credentialDatabase) AllowAnonymous() bool {
return c.allowAnonymous
}
// CredentialDatabaseOption is a function that modifies the credential database options.
type CredentialDatabaseOption func(*credentialDatabase)
// AllowGuest sets the guest access option for the credential database.
func AllowGuest(allow bool) CredentialDatabaseOption {
return func(db *credentialDatabase) {
db.allowGuest = allow
}
}
// AllowAnonymous sets the anonymous access option for the credential database.
func AllowAnonymous(allow bool) CredentialDatabaseOption {
return func(db *credentialDatabase) {
db.allowAnonymous = allow
}
}
// NewCredentialDatabase creates a new credential database with the given value and options.
func NewCredentialDatabase(value any, opts ...CredentialDatabaseOption) CredentialDatabase {
db := credentialDatabase{value: value}
for _, opt := range opts {
opt(&db)
}
return &db
}
+26
View File
@@ -3,6 +3,8 @@ package gssapi
import (
"context"
"encoding/asn1"
"github.com/oiweiwei/go-msrpc/ssp/name"
)
// Cap is a capability used both for request and response.
@@ -135,6 +137,10 @@ type Config struct {
// The flag that indicates whether it's a server
// handle.
IsServer bool
// The credential database.
CredentialDatabase CredentialDatabase
ServerNames []*name.Name
}
// MakeOption function is used to build the option structure.
@@ -214,6 +220,21 @@ func WithMechanismType(oid OID) Option {
}
}
// WithCredentialDatabase returns the option for the
// credential database.
func WithCredentialDatabase(db CredentialDatabase) Option {
return func(o *Config) {
o.CredentialDatabase = db
}
}
// WithServerNames returns the option for the server names.
func WithServerNames(names ...*name.Name) Option {
return func(o *Config) {
o.ServerNames = append(o.ServerNames, names...)
}
}
type MessageToken struct {
// The quality-of-protection.
QoP int
@@ -227,6 +248,11 @@ type MessageToken struct {
Signature []byte
}
func (m *MessageToken) Raw() []byte {
// The raw token is the concatenation of the payload and signature.
return append(m.Signature, m.Payload...)
}
// Per-Message Security Service Availability.
type SecurityService interface {
// MakeSignature function accepts the payload and returns the
+2
View File
@@ -59,6 +59,8 @@ type Mechanism interface {
Type() OID
// The security context init call.
Init(ctx context.Context, token *Token) (*Token, error)
// The capabilities of the token are defined by the mechanism.
Capabilities(ctx context.Context) Cap
// The security context accept call.
Accept(ctx context.Context, token *Token) (*Token, error)
// The maximum message size for the given limit. (and flag determining if
+121 -5
View File
@@ -261,11 +261,99 @@ func (a *Authentifier) APRequest(ctx context.Context) ([]byte, error) {
return b, nil
}
func (a *Authentifier) VerifyAPRequest(ctx context.Context, b []byte) ([]byte, error) {
if len(b) == 0 {
return nil, fmt.Errorf("krb5: verify apreq: empty token")
}
var err error
if a.service, err = a.makeService(ctx); err != nil {
return nil, fmt.Errorf("krb5: verify apreq: make service: %w", err)
}
if b[0] == 0x60 {
tok := &spnego.KRB5Token{}
if err := tok.Unmarshal(b); err != nil {
return nil, fmt.Errorf("krb5: verify apreq: unmarshal spnego: %w", err)
}
a.APReq = &tok.APReq
} else {
// dce style token.
a.APReq = &messages.APReq{}
if err := a.APReq.Unmarshal(b); err != nil {
return nil, fmt.Errorf("krb5: verify apreq: unmarshal: %w", err)
}
}
if ok, _, err := service.VerifyAPREQ(a.APReq, a.service); err != nil {
return nil, fmt.Errorf("krb5: verify apreq: verify apreq: %w", err)
} else if !ok {
return nil, fmt.Errorf("krb5: verify apreq: invalid apreq")
}
a.SessionKey = a.APReq.Ticket.DecryptedEncPart.Key
a.Config.Accept(a.APReq.Authenticator.Cksum.Checksum, a.APReq.APOptions)
if err := a.makeSecurityService(ctx); err != nil {
return nil, fmt.Errorf("krb5: verify apreq: make security service: %w", err)
}
if !a.Config.DCEStyle && !a.Config.FlagIsSet(gssapi.MutualAuthn) {
// no mutual authn, no aprep.
return nil, nil
}
encPart := messages.NewEncAPRepPart(a.APReq.Authenticator.SeqNumber)
encPart.Subkey = a.SessionKey
ap, err := messages.NewAPRep(a.SessionKey, encPart)
if err != nil {
return nil, fmt.Errorf("krb5: verify apreq: aprep: new aprep: %w", err)
}
b, err = ap.Marshal()
if err != nil {
return nil, fmt.Errorf("krb5: verify apreq: aprep: marshal: %w", err)
}
if err := a.makeSecurityService(ctx); err != nil {
return nil, fmt.Errorf("krb5: accept: make security service: %w", err)
}
a.APRep = &ap
return b, nil
}
func (a *Authentifier) VerifyAPReply(ctx context.Context, b []byte) error {
if !a.Config.DCEStyle {
tok := &spnego.KRB5Token{}
if err := tok.Unmarshal(b); err != nil {
return fmt.Errorf("krb5: verify: aprep: unmarshal: dcestyle: %w", err)
}
a.APRep = &tok.APRep
} else {
if err := a.APRep.Unmarshal(b); err != nil {
return fmt.Errorf("krb5: verify: aprep: unmarshal: %w", err)
}
}
if err := a.APRep.DecryptEncPart(a.SessionKey); err != nil {
return fmt.Errorf("krb5: verify: aprep: decrypt enc part: %w", err)
}
return nil
}
func (a *Authentifier) APReply(ctx context.Context, b []byte) ([]byte, error) {
if len(b) == 0 {
if err := a.makeSecurityService(ctx); err != nil {
return nil, fmt.Errorf("krb5: init: aprep: make security service: %w", err)
return nil, fmt.Errorf("krb5: init: aprep: b0: make security service: %w", err)
}
return nil, nil
}
@@ -305,6 +393,14 @@ func (a *Authentifier) APReply(ctx context.Context, b []byte) ([]byte, error) {
return b, nil
}
func (a *Authentifier) ParseInboundSignature(ctx context.Context, payload []byte) ([]byte, []byte, error) {
sgn, payload, err := a.state.InboundCipher.ParseSignature(ctx, payload)
if err != nil {
return nil, nil, fmt.Errorf("krb5: parse inbound signature: %w", err)
}
return sgn, payload, nil
}
func (a *Authentifier) MakeOutboundSignature(ctx context.Context, forSign [][]byte) ([]byte, error) {
sgn, err := a.state.OutboundCipher.MakeSignature(ctx, a.state.OutboundSequenceNumber, forSign)
if err != nil {
@@ -323,8 +419,17 @@ func (a *Authentifier) MakeInboundSignature(ctx context.Context, forSign [][]byt
return sgn, nil
}
func (a *Authentifier) WrapOutboundPayload(ctx context.Context, forSign, forSeal [][]byte) ([]byte, error) {
sgn, err := a.state.OutboundCipher.Wrap(ctx, a.state.OutboundSequenceNumber, forSign, forSeal)
func (a *Authentifier) WrapOutboundPayload(ctx context.Context, payload []byte, conf bool) ([]byte, error) {
sgn, err := a.state.OutboundCipher.Wrap(ctx, a.state.OutboundSequenceNumber, payload, conf)
if err != nil {
return nil, fmt.Errorf("krb5: wrap outbound payload: %w", err)
}
a.state.OutboundSequenceNumber++
return sgn, nil
}
func (a *Authentifier) WrapExOutboundPayload(ctx context.Context, forSign, forSeal [][]byte) ([]byte, error) {
sgn, err := a.state.OutboundCipher.WrapEx(ctx, a.state.OutboundSequenceNumber, forSign, forSeal)
if err != nil {
return nil, fmt.Errorf("krb5: wrap outbound payload: %w", err)
}
@@ -333,8 +438,8 @@ func (a *Authentifier) WrapOutboundPayload(ctx context.Context, forSign, forSeal
return sgn, nil
}
func (a *Authentifier) UnwrapInboundPayload(ctx context.Context, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
ok, err := a.state.InboundCipher.Unwrap(ctx, a.state.InboundSequenceNumber, forSign, forSeal, sgn)
func (a *Authentifier) UnwrapExInboundPayload(ctx context.Context, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
ok, err := a.state.InboundCipher.UnwrapEx(ctx, a.state.InboundSequenceNumber, forSign, forSeal, sgn)
if err != nil {
return ok, fmt.Errorf("krb5: unwrap inbound payload: %w", err)
}
@@ -344,6 +449,17 @@ func (a *Authentifier) UnwrapInboundPayload(ctx context.Context, forSign, forSea
return ok, nil
}
func (a *Authentifier) UnwrapInboundPayload(ctx context.Context, payload []byte, sgn []byte) ([]byte, bool, error) {
sgn, ok, err := a.state.InboundCipher.Unwrap(ctx, a.state.InboundSequenceNumber, payload, sgn)
if err != nil {
return sgn, ok, fmt.Errorf("krb5: unwrap inbound payload: %w", err)
}
if ok {
a.state.InboundSequenceNumber++
}
return sgn, ok, nil
}
func (a *Authentifier) OutboundSignatureSize(ctx context.Context, conf bool) int {
// use outbound cipher as we wrap the outbound data.
return a.state.OutboundCipher.Size(ctx, conf)
+50
View File
@@ -1,15 +1,19 @@
package krb5
import (
"encoding/binary"
"os"
"strings"
"time"
v8_config "github.com/jcmturner/gokrb5/v8/config"
"github.com/jcmturner/gofork/encoding/asn1"
"github.com/oiweiwei/gokrb5.fork/v9/client"
"github.com/oiweiwei/gokrb5.fork/v9/config"
"github.com/oiweiwei/gokrb5.fork/v9/iana/etypeID"
"github.com/oiweiwei/gokrb5.fork/v9/iana/flags"
"github.com/oiweiwei/gokrb5.fork/v9/service"
"github.com/oiweiwei/gokrb5.fork/v9/types"
@@ -83,6 +87,52 @@ type Config struct {
AnyServiceClassSPN bool
}
func (c *Config) Accept(ff []byte, apOptions asn1.BitString) {
if len(ff) > 24 {
c.Flags = append(c.Flags, int(gssapi.Delegation))
}
if len(ff) >= 24 {
f := binary.LittleEndian.Uint32(ff[20:24])
if f&uint32(gssapi.Anonymity) != 0 {
c.Flags = append(c.Flags, int(gssapi.Anonymity))
}
if f&uint32(gssapi.Confidentiality) != 0 {
c.Flags = append(c.Flags, int(gssapi.Confidentiality))
}
if f&uint32(gssapi.Integrity) != 0 {
c.Flags = append(c.Flags, int(gssapi.Integrity))
}
if f&uint32(gssapi.Identify) != 0 {
c.Flags = append(c.Flags, int(gssapi.Identify))
}
if f&uint32(gssapi.Datagram) != 0 {
c.Flags = append(c.Flags, int(gssapi.Datagram))
}
if f&uint32(gssapi.ReplayDetection) != 0 {
c.Flags = append(c.Flags, int(gssapi.ReplayDetection))
}
if f&uint32(gssapi.Sequencing) != 0 {
c.Flags = append(c.Flags, int(gssapi.Sequencing))
}
if f&uint32(gssapi.DCEStyle) != 0 {
c.Flags = append(c.Flags, int(gssapi.DCEStyle))
c.DCEStyle = true
}
}
if apOptions.At(flags.APOptionMutualRequired) != 0 {
c.APOptions = append(c.APOptions, flags.APOptionMutualRequired)
}
}
func (c *Config) GetKRB5Config() *config.Config {
switch conf := (any)(c.KRB5Config).(type) {
+55 -11
View File
@@ -79,19 +79,28 @@ func (c *AESCTSHMACSHA1) DeriveEncryptionKey() ([]byte, error) {
return c.setting.Type.DeriveKey(c.setting.Key.KeyValue, common.GetUsageKe(uint32(ecU)))
}
func (c *AESCTSHMACSHA1) Wrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte) ([]byte, error) {
b, err := c.wrap(ctx, seqNum, forSign, forSeal)
func (c *AESCTSHMACSHA1) Wrap(ctx context.Context, seqNum uint64, payload []byte, conf bool) ([]byte, error) {
var forSeal [][]byte
if conf {
forSeal = [][]byte{payload}
}
b, err := c.wrap(ctx, seqNum, [][]byte{payload}, forSeal, false)
if err != nil {
return nil, fmt.Errorf("aes-cts-hmac-sha1: wrap: %w", err)
}
return b, nil
}
func isSinglePayload(forSign, forSeal [][]byte) bool {
return len(forSign) == 1 && len(forSeal) == 1 && bytes.Equal(forSign[0], forSeal[0])
func (c *AESCTSHMACSHA1) WrapEx(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte) ([]byte, error) {
b, err := c.wrap(ctx, seqNum, forSign, forSeal, true)
if err != nil {
return nil, fmt.Errorf("aes-cts-hmac-sha1: wrap_ex: %w", err)
}
return b, nil
}
func (c *AESCTSHMACSHA1) wrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte) ([]byte, error) {
func (c *AESCTSHMACSHA1) wrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, isEx bool) ([]byte, error) {
eB, hdr := bytes.NewBuffer(nil), c.WrapHeader(ctx, seqNum)
@@ -103,7 +112,7 @@ func (c *AESCTSHMACSHA1) wrap(ctx context.Context, seqNum uint64, forSign, forSe
ec := EC
if isSinglePayload(forSign, forSeal) /* it's a Wrap call, compute EC according to RFC 4121, section 4.3.2. */ {
if !isEx /* it's a Wrap call, compute EC according to RFC 4121, section 4.3.2. */ {
sz, block := 0, c.setting.Type.GetMessageBlockByteSize()
for _, b := range forSeal {
sz += len(b)
@@ -115,7 +124,7 @@ func (c *AESCTSHMACSHA1) wrap(ctx context.Context, seqNum uint64, forSign, forSe
// gen ec.
ecB := bytes.Repeat([]byte{0xFF}, ec)
// set ec value (16). (pad = 1, block_size = 16).
// set ec value.
binary.BigEndian.PutUint16(hdr[4:6], uint16(ec))
iH, err := c.IntegrityHash()
@@ -156,15 +165,50 @@ func (c *AESCTSHMACSHA1) wrap(ctx context.Context, seqNum uint64, forSign, forSe
return append(hdr, sgn...), nil
}
func (c *AESCTSHMACSHA1) Unwrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
ok, err := c.unwrap(ctx, seqNum, forSign, forSeal, sgn)
func (c *AESCTSHMACSHA1) Unwrap(ctx context.Context, seqNum uint64, payload []byte, sgn []byte) ([]byte, bool, error) {
var err error
if len(sgn) == 0 {
if sgn, payload, err = c.ParseSignature(ctx, payload); err != nil {
return nil, false, fmt.Errorf("aes-cts-hmac-sha1: unwrap: parse token: %w", err)
}
}
ok, err := c.unwrapEx(ctx, seqNum, [][]byte{payload}, [][]byte{payload}, sgn)
if err != nil {
return ok, fmt.Errorf("aes-cts-hmac-sha1: unwrap: %w", err)
return nil, false, fmt.Errorf("aes-cts-hmac-sha1: unwrap: %w", err)
}
return sgn, ok, nil
}
func (c *AESCTSHMACSHA1) UnwrapEx(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
ok, err := c.unwrapEx(ctx, seqNum, forSign, forSeal, sgn)
if err != nil {
return ok, fmt.Errorf("aes-cts-hmac-sha1: unwrap_ex: %w", err)
}
return ok, nil
}
func (c *AESCTSHMACSHA1) unwrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
// ParseSignature parses the header of the signature and returns the header and the remaining of the signature.
func (c *AESCTSHMACSHA1) ParseSignature(ctx context.Context, payload []byte) ([]byte, []byte, error) {
if len(payload) < 16 {
return nil, nil, fmt.Errorf("invalid payload size: %d < 16", len(payload))
}
rrc, ec := int(binary.BigEndian.Uint16(payload[6:])), int(binary.BigEndian.Uint16(payload[4:]))
sgn := ec + rrc + 16 /* hdr */ + c.setting.Type.GetConfounderByteSize()
if len(payload) < sgn {
return nil, nil, fmt.Errorf("invalid payload size: %d < %d", len(payload), sgn)
}
return payload[:sgn], payload[sgn:], nil
}
func (c *AESCTSHMACSHA1) unwrapEx(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
// buffer for decryption.
eB, hdr := bytes.NewBuffer(nil), sgn[:16]
+18 -16
View File
@@ -68,17 +68,17 @@ func EncodeASN1ValueWithExtraSize(b []byte, oid asn1.ObjectIdentifier, extra ...
// ParseASN1Value function parses the given byte slice as an ASN.1 value. The
// function returns the remaining data, the object identifier and an error if
// any.
func ParseASN1Value(b []byte, extra [][]byte) ([]byte, asn1.ObjectIdentifier, error) {
func ParseASN1Value(b []byte, extra [][]byte) ([]byte, int, asn1.ObjectIdentifier, error) {
var oid asn1.ObjectIdentifier
if !IsASN1Value(b) {
return b, oid, nil
return b, 0, oid, nil
}
b, sz, ok := DecodeASN1Header(b)
b, oft, sz, ok := DecodeASN1Header(b)
if !ok {
return nil, oid, fmt.Errorf("invalid asn1 size header")
return nil, 0, oid, fmt.Errorf("invalid asn1 size header")
}
extraL := uint32(0)
@@ -95,10 +95,10 @@ func ParseASN1Value(b []byte, extra [][]byte) ([]byte, asn1.ObjectIdentifier, er
// trim oid.
b, err := asn1.Unmarshal(b, &oid)
if err != nil {
return nil, oid, fmt.Errorf("unmarshal oid: %w", err)
return nil, 0, oid, fmt.Errorf("unmarshal oid: %w", err)
}
return b, oid, nil
return b, oft + (int(sz) - len(b)), oid, nil
}
// EncodeASN1Header function encodes the given size into an ASN.1 header and
@@ -148,35 +148,37 @@ func EncodeASN1HeaderTo(b []byte, sz uint32) (int, error) {
// DecodeASN1Header function decodes the given byte slice as an ASN.1 header and
// returns the remaining data, the size and a boolean value indicating if the
// header is valid.
func DecodeASN1Header(b []byte) ([]byte, uint32, bool) {
func DecodeASN1Header(b []byte) ([]byte, int, uint32, bool) {
oft := 0
if len(b) == 0 || b[0] != 0x60 {
// not an asn1 value.
return b, uint32(len(b)), true
return b, 0, uint32(len(b)), true
}
if b = b[1:]; len(b) < 1 {
if b, oft = b[1:], oft+1; len(b) < 1 {
// trim asn1 app tag.
return nil, 0, false
return nil, oft, 0, false
}
var sz uint32
if b, sz = b[1:], uint32(b[0]); sz <= 0x7F {
if b, sz, oft = b[1:], uint32(b[0]), oft+1; sz <= 0x7F {
// trim primitive size.
return b, sz, true
return b, oft, sz, true
}
if sz&0x80 != 0x80 || (sz&0x0F) > uint32(len(b)) {
// check number of bytes.
return nil, 0, false
return nil, 0, 0, false
}
sz &= 0x0F
var cnt int
switch cnt = int(sz); cnt {
switch cnt, oft = int(sz), oft+int(sz); cnt {
case 1:
b, sz = b[cnt:], uint32(b[0])
case 2:
@@ -186,8 +188,8 @@ func DecodeASN1Header(b []byte) ([]byte, uint32, bool) {
case 4:
b, sz = b[cnt:], uint32(b[0])<<24|uint32(b[1])<<16|uint32(b[2])<<8|uint32(b[3])
default:
return nil, 0, false
return nil, oft, 0, false
}
return b, sz, true
return b, oft, sz, true
}
+5 -2
View File
@@ -10,8 +10,11 @@ import (
)
type Cipher interface {
Wrap(context.Context, uint64, [][]byte, [][]byte) ([]byte, error)
Unwrap(context.Context, uint64, [][]byte, [][]byte, []byte) (bool, error)
Wrap(context.Context, uint64, []byte, bool) ([]byte, error)
WrapEx(context.Context, uint64, [][]byte, [][]byte) ([]byte, error)
UnwrapEx(context.Context, uint64, [][]byte, [][]byte, []byte) (bool, error)
Unwrap(context.Context, uint64, []byte, []byte) ([]byte, bool, error)
ParseSignature(context.Context, []byte) ([]byte, []byte, error)
MakeSignature(context.Context, uint64, [][]byte) ([]byte, error)
Size(context.Context, bool) int
}
+55 -5
View File
@@ -17,7 +17,39 @@ func NewDESCipher(ctx context.Context, setting CipherSetting) (Cipher, error) {
return &DES{setting: setting}, nil
}
func (c *DES) Wrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte) ([]byte, error) {
// ParseHeader extracts the header and payload from the token.
func (c *DES) ParseSignature(ctx context.Context, payload []byte) ([]byte, []byte, error) {
_, oft, _, err := ParseASN1Value(payload, [][]byte{payload})
if err != nil {
return nil, nil, fmt.Errorf("des: parse header: %w", err)
}
if len(payload) < oft+4 {
return nil, nil, fmt.Errorf("des: parse header: invalid payload size: %d < %d", len(payload), oft)
}
switch payload[oft+4] {
case 0x02 /* wrap token */ :
oft += 32
case 0x01 /* mic token */ :
oft += 24
default:
return nil, nil, fmt.Errorf("des: parse header: invalid token type: %d", payload[oft+4])
}
return payload[:oft], payload[oft:], nil
}
func (c *DES) Wrap(ctx context.Context, seqNum uint64, payload []byte, conf bool) ([]byte, error) {
var forSeal [][]byte
if conf {
forSeal = [][]byte{payload}
}
return c.WrapEx(ctx, seqNum, [][]byte{payload}, forSeal)
}
func (c *DES) WrapEx(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte) ([]byte, error) {
b, err := c.wrap(ctx, seqNum, forSign, forSeal)
if err != nil {
return nil, fmt.Errorf("des: wrap: %w", err)
@@ -62,15 +94,33 @@ func (c *DES) wrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte
return EncodeASN1Value(tok.Marshal(), KRB5OID, true /* always use dce-style */, forSign)
}
func (c *DES) Unwrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
ok, err := c.unwrap(ctx, seqNum, forSign, forSeal, sgn)
func (c *DES) Unwrap(ctx context.Context, seqNum uint64, payload []byte, sgn []byte) ([]byte, bool, error) {
var err error
if len(sgn) == 0 {
// if sgn is empty, it's encoded as part of the payload.
if sgn, payload, err = c.ParseSignature(ctx, payload); err != nil {
return nil, false, fmt.Errorf("des: unwrap: parse token: %w", err)
}
}
ok, err := c.unwrapEx(ctx, seqNum, [][]byte{payload}, [][]byte{payload}, sgn)
if err != nil {
return nil, false, fmt.Errorf("des: unwrap: %w", err)
}
return sgn, ok, err
}
func (c *DES) UnwrapEx(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
ok, err := c.unwrapEx(ctx, seqNum, forSign, forSeal, sgn)
if err != nil {
return ok, fmt.Errorf("des: unwrap: %w", err)
}
return ok, nil
}
func (c *DES) unwrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
func (c *DES) unwrapEx(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
key := c.setting.Key
@@ -78,7 +128,7 @@ func (c *DES) unwrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]by
expTok.SetSequenceNumber(uint32(seqNum), c.setting.IsLocal)
// trim asn1 header.
sgn, _, err := ParseASN1Value(sgn, forSign)
sgn, _, _, err := ParseASN1Value(sgn, forSign)
if err != nil {
return false, gssapi.ErrDefectiveToken
}
+55 -5
View File
@@ -17,7 +17,38 @@ func NewRC4Cipher(ctx context.Context, setting CipherSetting) (Cipher, error) {
return &RC4HMAC{setting: setting}, nil
}
func (c *RC4HMAC) Wrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte) ([]byte, error) {
func (c *RC4HMAC) ParseSignature(ctx context.Context, payload []byte) ([]byte, []byte, error) {
_, oft, _, err := ParseASN1Value(payload, [][]byte{payload})
if err != nil {
return nil, nil, fmt.Errorf("des: parse header: %w", err)
}
if len(payload) < oft+4 {
return nil, nil, fmt.Errorf("des: parse header: invalid payload size: %d < %d", len(payload), oft)
}
switch payload[oft+4] {
case 0x02 /* wrap token */ :
oft += 32
case 0x01 /* mic token */ :
oft += 24
default:
return nil, nil, fmt.Errorf("des: parse header: invalid token type: %d", payload[oft+4])
}
return payload[:oft], payload[oft:], nil
}
func (c *RC4HMAC) Wrap(ctx context.Context, seqNum uint64, payload []byte, conf bool) ([]byte, error) {
var forSeal [][]byte
if conf {
forSeal = [][]byte{payload}
}
return c.WrapEx(ctx, seqNum, [][]byte{payload}, forSeal)
}
func (c *RC4HMAC) WrapEx(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte) ([]byte, error) {
b, err := c.wrap(ctx, seqNum, forSign, forSeal)
if err != nil {
return nil, fmt.Errorf("rc4-hmac: wrap: %w", err)
@@ -63,15 +94,34 @@ func (c *RC4HMAC) wrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]
return EncodeASN1Value(tok.Marshal(), KRB5OID, true /* always use dce-style */, forSign)
}
func (c *RC4HMAC) Unwrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
ok, err := c.unwrap(ctx, seqNum, forSign, forSeal, sgn)
func (c *RC4HMAC) Unwrap(ctx context.Context, seqNum uint64, payload []byte, sgn []byte) ([]byte, bool, error) {
var err error
if len(sgn) == 0 {
// if sgn is empty, it's encoded as part of the payload.
if sgn, payload, err = c.ParseSignature(ctx, payload); err != nil {
return nil, false, fmt.Errorf("rc4-hmac: unwrap: parse token: %w", err)
}
}
ok, err := c.unwrapEx(ctx, seqNum, [][]byte{payload}, [][]byte{payload}, sgn)
if err != nil {
return nil, false, fmt.Errorf("rc4-hmac: unwrap: %w", err)
}
return sgn, ok, nil
}
func (c *RC4HMAC) UnwrapEx(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
ok, err := c.unwrapEx(ctx, seqNum, forSign, forSeal, sgn)
if err != nil {
return ok, fmt.Errorf("rc4-hmac: unwrap: %w", err)
}
return ok, nil
}
func (c *RC4HMAC) unwrap(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
func (c *RC4HMAC) unwrapEx(ctx context.Context, seqNum uint64, forSign, forSeal [][]byte, sgn []byte) (bool, error) {
key := c.setting.Key.KeyValue
@@ -79,7 +129,7 @@ func (c *RC4HMAC) unwrap(ctx context.Context, seqNum uint64, forSign, forSeal []
expTok.SetSequenceNumber(uint32(seqNum), c.setting.IsLocal)
// trim asn1 header.
sgn, _, err := ParseASN1Value(sgn, forSign)
sgn, _, _, err := ParseASN1Value(sgn, forSign)
if err != nil {
return false, gssapi.ErrDefectiveToken
}
+112 -26
View File
@@ -165,9 +165,87 @@ func (m *Mechanism) Init(ctx context.Context, tok *gssapi.Token) (*gssapi.Token,
return &gssapi.Token{Payload: b}, gssapi.ContextContinueNeeded(ctx)
}
func (m *Mechanism) Capabilities(ctx context.Context) gssapi.Cap {
caps := gssapi.Cap(0)
if m.Config.FlagIsSet(gssapi.Anonymity) {
caps |= gssapi.Anonymity
}
if m.Config.FlagIsSet(gssapi.Identify) {
caps |= gssapi.Identify
}
if m.Config.FlagIsSet(gssapi.MutualAuthn) {
caps |= gssapi.MutualAuthn
}
if m.Config.FlagIsSet(gssapi.ReplayDetection) {
caps |= gssapi.ReplayDetection
}
if m.Config.FlagIsSet(gssapi.Sequencing) {
caps |= gssapi.Sequencing
}
if m.Config.FlagIsSet(gssapi.Integrity) {
caps |= gssapi.Integrity
}
if m.Config.FlagIsSet(gssapi.Confidentiality) {
caps |= gssapi.Confidentiality
}
if m.Config.FlagIsSet(gssapi.DCEStyle) || m.Config.DCEStyle {
caps |= gssapi.DCEStyle
}
for _, o := range m.Config.APOptions {
if o == flags.APOptionMutualRequired {
caps |= gssapi.MutualAuthn
}
}
return caps
}
// The security context accept call.
func (m *Mechanism) Accept(ctx context.Context, tok *gssapi.Token) (*gssapi.Token, error) {
return nil, gssapi.ContextError(ctx, gssapi.Unavailable, gssapi.ErrUnavailable)
if m.APRep != nil {
if err := m.VerifyAPReply(ctx, tok.Payload); err != nil {
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
gssapi.SetAttribute(ctx, gssapi.AttributeSessionKey, m.ExportedSessionKey)
gssapi.SetAttribute(ctx, gssapi.AttributeTarget, m.Config.SName)
return &gssapi.Token{}, gssapi.ContextComplete(ctx)
}
if m.APReq != nil {
b, err := m.APReply(ctx, tok.Payload)
if err != nil {
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
gssapi.SetAttribute(ctx, gssapi.AttributeSessionKey, m.ExportedSessionKey)
gssapi.SetAttribute(ctx, gssapi.AttributeTarget, m.Config.SName)
return &gssapi.Token{Payload: b}, gssapi.ContextComplete(ctx)
}
b, err := m.VerifyAPRequest(ctx, tok.Payload)
if err != nil {
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
if !m.Config.DCEStyle && !m.Config.FlagIsSet(gssapi.MutualAuthn) /* for non-dce style, there will be no APReply */ {
gssapi.SetAttribute(ctx, gssapi.AttributeSessionKey, m.ExportedSessionKey)
gssapi.SetAttribute(ctx, gssapi.AttributeTarget, m.Config.SName)
return &gssapi.Token{Payload: b}, gssapi.ContextComplete(ctx)
}
return &gssapi.Token{Payload: b}, gssapi.ContextContinueNeeded(ctx)
}
// The maximum message size for the given limit. (and flag determining if
@@ -192,7 +270,7 @@ func (m *Mechanism) WrapEx(ctx context.Context, tokEx *gssapi.MessageTokenEx) (*
var err error
tokEx.Signature, err = m.WrapOutboundPayload(ctx, forSign, forSeal)
tokEx.Signature, err = m.WrapExOutboundPayload(ctx, forSign, forSeal)
if err != nil {
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
@@ -214,7 +292,7 @@ func (m *Mechanism) UnwrapEx(ctx context.Context, tokEx *gssapi.MessageTokenEx)
}
}
ok, err := m.UnwrapInboundPayload(ctx, forSign, forSeal, tokEx.Signature)
ok, err := m.UnwrapExInboundPayload(ctx, forSign, forSeal, tokEx.Signature)
if err != nil {
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
@@ -230,21 +308,18 @@ func (m *Mechanism) UnwrapEx(ctx context.Context, tokEx *gssapi.MessageTokenEx)
// Wrap token.
func (m *Mechanism) Wrap(ctx context.Context, tok *gssapi.MessageToken) (*gssapi.MessageToken, error) {
tokEx, err := m.WrapEx(ctx, &gssapi.MessageTokenEx{
QoP: tok.QoP,
Payloads: []*gssapi.PayloadEx{
{Capabilities: tok.Capabilities, Payload: tok.Payload},
},
})
var err error
tok.Signature, err = m.WrapOutboundPayload(ctx, tok.Payload, tok.Capabilities.IsSet(gssapi.Confidentiality))
if err != nil {
return nil, err
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
return &gssapi.MessageToken{
QoP: tokEx.QoP,
Capabilities: tokEx.Payloads[0].Capabilities,
Payload: tokEx.Payloads[0].Payload,
Signature: tokEx.Signature,
QoP: tok.QoP,
Capabilities: tok.Capabilities,
Payload: tok.Payload,
Signature: tok.Signature,
}, nil
}
@@ -252,22 +327,25 @@ func (m *Mechanism) Wrap(ctx context.Context, tok *gssapi.MessageToken) (*gssapi
// Unwrap token.
func (m *Mechanism) Unwrap(ctx context.Context, tok *gssapi.MessageToken) (*gssapi.MessageToken, error) {
tokEx, err := m.UnwrapEx(ctx, &gssapi.MessageTokenEx{
QoP: tok.QoP,
Payloads: []*gssapi.PayloadEx{
{Capabilities: tok.Capabilities, Payload: tok.Payload},
},
Signature: tok.Signature,
})
sgn, ok, err := m.UnwrapInboundPayload(ctx, tok.Payload, tok.Signature)
if err != nil {
return nil, err
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
if !ok {
return nil, gssapi.ContextError(ctx, gssapi.BadMIC, gssapi.ErrBadMIC)
}
if len(tok.Signature) == 0 && len(sgn) > 0 {
tok.Payload = tok.Payload[len(sgn):]
tok.Signature = sgn
}
return &gssapi.MessageToken{
QoP: tokEx.QoP,
Capabilities: tokEx.Payloads[0].Capabilities,
Payload: tokEx.Payloads[0].Payload,
Signature: tokEx.Signature,
QoP: tok.QoP,
Capabilities: tok.Capabilities,
Payload: tok.Payload,
Signature: tok.Signature,
}, nil
}
@@ -312,6 +390,14 @@ func (m *Mechanism) MakeSignatureEx(ctx context.Context, tokEx *gssapi.MessageTo
// VerifySignature token.
func (m *Mechanism) VerifySignature(ctx context.Context, tok *gssapi.MessageToken) error {
var err error
if len(tok.Signature) == 0 {
if tok.Signature, tok.Payload, err = m.ParseInboundSignature(ctx, tok.Payload); err != nil {
return gssapi.ContextError(ctx, gssapi.Failure, err)
}
}
expSgn, err := m.MakeInboundSignature(ctx, [][]byte{tok.Payload})
if err != nil {
return gssapi.ContextError(ctx, gssapi.Failure, err)
+78
View File
@@ -0,0 +1,78 @@
package name
import "strings"
// NameType represents the type of naming convention used.
type NameType int
const (
UnknownNameType NameType = iota
// DNSNameType represents a DNS name, e.g. host.domain.tld
DNSNameType
// NetBIOSNameType represents a NetBIOS name, e.g. DOMAIN\host
NetBIOSNameType
)
// Name represents a computer name with optional domain and forest components.
type Name struct {
// Type indicates the naming convention used (DNS or NetBIOS).
Type NameType
// Name is the primary identifier, either the hostname or NetBIOS name.
Name string
// Domain is the domain part of the name, if applicable.
Domain string
// Forest is an optional higher-level grouping, often used in Active Directory contexts.
Forest string
}
type NameOption func(*Name)
// Forest sets the Forest field of the Name struct.
func Forest(forest string) NameOption {
return func(n *Name) {
n.Forest = forest
}
}
// Domain sets the Domain field of the Name struct.
func Domain(domain string) NameOption {
return func(n *Name) {
n.Domain = domain
}
}
// NetBIOS creates a Name instance using the NetBIOS naming convention.
func NetBIOS(name string, o ...NameOption) *Name {
n := &Name{Type: NetBIOSNameType, Name: name}
if n.Name = strings.TrimLeft(n.Name, "\\"); strings.Contains(n.Name, "\\") {
parts := strings.SplitN(n.Name, "\\", 2)
n.Name = parts[1]
n.Domain = parts[0]
}
for _, opt := range o {
opt(n)
}
return n
}
// DNS creates a Name instance using the DNS naming convention.
func DNS(name string, o ...NameOption) *Name {
n := &Name{Type: DNSNameType, Name: name}
if strings.Contains(n.Name, ".") {
parts := strings.SplitN(n.Name, ".", 2)
n.Name = parts[0]
n.Domain = parts[1]
}
for _, opt := range o {
opt(n)
}
return n
}
+4
View File
@@ -110,6 +110,10 @@ func (m *Mechanism) Init(ctx context.Context, tok *gssapi.Token) (*gssapi.Token,
return &gssapi.Token{Payload: b}, gssapi.ContextContinueNeeded(ctx)
}
func (m *Mechanism) Capabilities(ctx context.Context) gssapi.Cap {
return gssapi.Cap(0)
}
// The security context accept call.
func (m *Mechanism) Accept(ctx context.Context, tok *gssapi.Token) (*gssapi.Token, error) {
return nil, gssapi.ContextError(ctx, gssapi.Unavailable, gssapi.ErrUnavailable)
+164 -7
View File
@@ -5,7 +5,9 @@ import (
"context"
"fmt"
"hash"
"time"
"github.com/oiweiwei/go-msrpc/ssp/credential"
"github.com/oiweiwei/go-msrpc/ssp/crypto"
)
@@ -196,7 +198,7 @@ func (a *Authentifier) Authenticate(ctx context.Context, b []byte) ([]byte, erro
return nil, fmt.Errorf("ntlm: init: authenticate: compute mic: %w", err)
}
// copy mic value.
copy(b[micidx:], am.MIC)
copy(b[MICOffset:], am.MIC)
// reset the mic buffer.
a.mic.Reset()
}
@@ -204,8 +206,161 @@ func (a *Authentifier) Authenticate(ctx context.Context, b []byte) ([]byte, erro
return b, nil
}
func (a *Authentifier) Challenge(ctx context.Context, b []byte) ([]byte, error) {
var err error
nm := &NegotiateMessage{}
if err := nm.Unmarshal(ctx, b); err != nil {
return nil, fmt.Errorf("ntlm: init: challenge: unmarshal negotiate message: %w", err)
}
// accept the negotiated flags.
a.Config.Accept(nm.Negotiate)
a.mic.Write(b)
cm := &ChallengeMessage{
Negotiate: nm.Negotiate | RequestTarget | NegotiateNTLM | NegotiateAlwaysSign,
TargetInfo: AttrValues{},
}
if cm.Negotiate.IsSet(NegotiateUnicode) {
cm.Negotiate = cm.Negotiate.Unset(NegotiateOEM)
} else {
cm.Negotiate = cm.Negotiate.Set(NegotiateOEM)
}
if nm.Negotiate.IsSet(NegotiateExtendedSessionSecurity) {
cm.Negotiate = cm.Negotiate.Unset(NegotiateLMKey)
}
if cm.Negotiate.IsSet(TargetTypeDomain) || a.Config.NetBIOSDomainName != "" {
cm.TargetName = a.Config.NetBIOSDomainName
cm.Negotiate = cm.Negotiate.Set(TargetTypeDomain).Unset(TargetTypeServer)
} else {
cm.TargetName = a.Config.NetBIOSComputerName
cm.Negotiate = cm.Negotiate.Set(TargetTypeServer).Unset(TargetTypeDomain)
}
cm.Negotiate = cm.Negotiate.Set(NegotiateTargetInfo).Set(RequestTarget)
// if err := a.Config.Verify(cm.Negotiate); err != nil {
// return nil, fmt.Errorf("ntlm: init: challenge: %w", err)
// }
if a.Config.NetBIOSComputerName != "" {
cm.TargetInfo[AttrNetBIOSComputerName] = &Value{NetBIOSComputerName: a.Config.NetBIOSComputerName}
}
if a.Config.NetBIOSDomainName != "" {
cm.TargetInfo[AttrNetBIOSDomainName] = &Value{NetBIOSDomainName: a.Config.NetBIOSDomainName}
}
if a.Config.DNSDomainName != "" {
cm.TargetInfo[AttrDNSDomainName] = &Value{DNSDomainName: a.Config.DNSDomainName}
}
if a.Config.DNSComputerName != "" {
cm.TargetInfo[AttrDNSComputerName] = &Value{DNSComputerName: a.Config.DNSComputerName}
}
if a.Config.DNSForestName != "" {
cm.TargetInfo[AttrDNSTreeName] = &Value{DNSTreeName: a.Config.DNSForestName}
}
if a.Config.RequestMIC {
cm.TargetInfo[AttrTimestamp] = &Value{Timestamp: TimeToFiletime(time.Now())}
}
if cm.ServerChallenge, err = crypto.Nonce(8); err != nil {
return nil, fmt.Errorf("ntlm: init: challenge: generate server challenge: %w", err)
}
if cm.Negotiate.IsSet(NegotiateVersion) {
if cm.Version = a.Config.Version; cm.Version.IsZero() {
cm.Version = DefaultVersion
}
}
if b, err = cm.Marshal(ctx); err != nil {
return nil, fmt.Errorf("ntlm: init: challenge: marshal challenge message: %w", err)
}
a.makeSecurityParameters(ctx, cm)
a.mic.Write(b)
return b, nil
}
func (a *Authentifier) VerifyAuthenticate(ctx context.Context, b []byte) error {
am := &AuthenticateMessage{}
if err := am.Unmarshal(ctx, b); err != nil {
return fmt.Errorf("ntlm: init: verify authenticate: unmarshal authenticate message: %w", err)
}
if len(am.MIC) == 0 && a.Config.RequestMIC {
return fmt.Errorf("ntlm: init: verify authenticate: mic is required but not provided")
}
copy(b[MICOffset:], make([]byte, 16))
a.mic.Write(b)
var cred credential.Credential
cred = credential.NewFromPassword(am.UserName, "",
credential.Domain(am.DomainName),
credential.Workstation(am.Workstation),
)
nonce := a.session.ServerChallenge
resp, err := a.Config.Verifier.VerifyChallenge(ctx, a.session, cred, nonce, am)
if err != nil {
if am.UserName == "" {
return fmt.Errorf("ntlm: init: verify authenticate: verify challenge response: %w", err)
}
cred = credential.New(am.UserName,
credential.Workstation(am.Workstation),
)
if resp, err = a.Config.Verifier.VerifyChallenge(ctx, a.session, cred, nonce, am); err != nil {
return fmt.Errorf("ntlm: init: verify authenticate: verify challenge response: %w", err)
}
}
var exportedKey []byte
if a.session.KeyExchange {
if exportedKey, err = RC4K(ctx, resp.KeyExchangeKey, am.EncryptedRandomSessionKey); err != nil {
return fmt.Errorf("ntlm: local provider: rc4k failed: %w", err)
}
} else {
exportedKey = resp.KeyExchangeKey
}
expectedMIC, err := crypto.HMACMD5(exportedKey, a.mic.Bytes())
if err != nil {
return fmt.Errorf("ntlm: init: authenticate: compute mic: %w", err)
}
if !bytes.Equal(am.MIC, expectedMIC) {
return fmt.Errorf("ntlm: init: verify authenticate: mic mismatch")
}
if err = a.makeSecurityService(ctx, exportedKey); err != nil {
return fmt.Errorf("ntlm: init: verify authenticate: make security service: %w", err)
}
return nil
}
// The MIC index in the output buffer.
const micidx = 72
const MICOffset = 72
var (
// client-to-server seal.
@@ -283,7 +438,7 @@ func (a *Authentifier) makeSecurityService(ctx context.Context, key []byte) erro
}
// makeSignKey creates the key for the signature.
func (a *Authentifier) makeSignKey(ctx context.Context, key []byte, magic []byte) ([]byte, error) {
func (a *Authentifier) makeSignKey(_ context.Context, key []byte, magic []byte) ([]byte, error) {
if !a.session.ExtendedSessionSecurity {
return nil, nil
}
@@ -299,10 +454,11 @@ type SecurityParameters struct {
KeySize int
ServerName string
DomainName string
ServerChallenge []byte
}
// makeHashFunc creates the hash function.
func (a *Authentifier) makeHashFunc(ctx context.Context, key []byte) func(uint32) hash.Hash {
func (a *Authentifier) makeHashFunc(_ context.Context, key []byte) func(uint32) hash.Hash {
if a.session.ExtendedSessionSecurity {
return HMACMD5(key)
}
@@ -310,7 +466,7 @@ func (a *Authentifier) makeHashFunc(ctx context.Context, key []byte) func(uint32
}
// makeSealKey creates the key for the encryption.
func (a *Authentifier) makeSealKey(ctx context.Context, key []byte, magic []byte) ([]byte, error) {
func (a *Authentifier) makeSealKey(_ context.Context, key []byte, magic []byte) ([]byte, error) {
if a.session.ExtendedSessionSecurity {
switch a.session.KeySize {
@@ -339,7 +495,7 @@ const (
SignatureVersion = 0x01
)
func (a *Authentifier) makeSecurityParameters(ctx context.Context, cm *ChallengeMessage) {
func (a *Authentifier) makeSecurityParameters(_ context.Context, cm *ChallengeMessage) {
a.session = &SecurityParameters{
ExtendedSessionSecurity: cm.Negotiate.IsSet(NegotiateExtendedSessionSecurity),
UseLMKey: cm.Negotiate.IsSet(NegotiateLMKey),
@@ -347,6 +503,7 @@ func (a *Authentifier) makeSecurityParameters(ctx context.Context, cm *Challenge
KeyExchange: cm.Negotiate.IsSet(NegotiateKeyExchange),
Datagram: cm.Negotiate.IsSet(NegotiateDatagram),
KeySize: 40,
ServerChallenge: cm.ServerChallenge,
}
if cm.Negotiate.IsSet(TargetTypeDomain) {
@@ -408,7 +565,7 @@ func (a *Authentifier) MakeOutboundChecksum(ctx context.Context, b [][]byte) ([]
return chk, nil
}
func (a *Authentifier) makeChecksum(ctx context.Context, cipher *Cipher, seqNum uint32, b ...[]byte) ([]byte, error) {
func (a *Authentifier) makeChecksum(_ context.Context, cipher *Cipher, seqNum uint32, b ...[]byte) ([]byte, error) {
if !a.Config.Integrity {
return nil, nil
}
+44
View File
@@ -117,6 +117,25 @@ type Config struct {
// The flag that indicates whether all the input buffers must be used to
// build a signature. (DO NOT USE IT).
NoSignAllBuffers bool
ServerConfigFlags Flag
// The NetBIOS computer name of the server.
NetBIOSComputerName string
// The NetBIOS domain name of the server.
NetBIOSDomainName string
// The DNS computer name of the server.
DNSComputerName string
// The DNS domain name of the server.
DNSDomainName string
// The DNS forest name of the server.
DNSForestName string
// Allow the use of the null session.
AllowAnonymous bool
// Allow the use of the guest account.
AllowGuest bool
// The credential verifier.
Verifier Verifier
}
func IsCredentialEmpty(cred any) bool {
@@ -212,6 +231,31 @@ func (c *Config) Verify(f Flag) error {
return nil
}
func (c *Config) Accept(flags Flag) {
c.Integrity = true
if flags.IsSet(NegotiateSign) {
c.Integrity = true
}
if flags.IsSet(NegotiateSeal) {
c.Confidentiality = true
}
if flags.IsSet(Anonymous) {
c.Anonymity = true
}
if flags.IsSet(NegotiateIdentify) {
c.Identify = true
}
if flags.IsSet(NegotiateDatagram) {
c.Datagram = true
}
}
// Negotiate function returns the set of negotiated flags.
func (c *Config) Negotiate() Flag {
+125 -7
View File
@@ -6,7 +6,9 @@ import (
"errors"
"time"
"github.com/oiweiwei/go-msrpc/ssp/credential"
"github.com/oiweiwei/go-msrpc/ssp/gssapi"
"github.com/oiweiwei/go-msrpc/ssp/name"
)
var (
@@ -23,6 +25,37 @@ func (Config) Type() gssapi.OID {
return MechanismType
}
func (c *Config) Capabilities() gssapi.Cap {
var caps gssapi.Cap
if c.Integrity {
caps |= gssapi.Integrity | gssapi.ReplayDetection | gssapi.Sequencing
}
if c.Confidentiality {
caps |= gssapi.Confidentiality
}
if c.Identify {
caps |= gssapi.Identify
}
if c.Anonymity {
caps |= gssapi.Anonymity
}
if c.Datagram {
caps |= gssapi.Datagram
}
if c.Anonymity {
caps |= gssapi.Anonymity
}
return caps
}
// Copy function returns the copy of the configuration.
func (c *Config) Copy() gssapi.MechanismConfig {
@@ -61,11 +94,65 @@ func (Mechanism) New(ctx context.Context) (gssapi.Mechanism, error) {
// config should have been populated.
return nil, gssapi.ContextError(ctx, gssapi.NoContext, gssapi.ErrNoContext)
}
if cc.Credential != nil {
c.IsServer = cc.IsServer
if !c.IsServer && cc.Credential != nil {
if c.Credential, ok = cc.Credential.Value().(Credential); !ok || !IsValidCredential(c.Credential) {
return nil, gssapi.ContextError(ctx, gssapi.DefectiveCredential, gssapi.ErrDefectiveCredential)
}
}
if cc.CredentialDatabase != nil {
if db, ok := cc.CredentialDatabase.Value().(credential.Database); ok {
if cc.CredentialDatabase.AllowAnonymous() {
c.AllowAnonymous = true
}
if cc.CredentialDatabase.AllowGuest() {
c.AllowGuest = true
}
c.Verifier = &LocalVerifier{
Config: c,
Database: db,
}
}
} else {
if c.Verifier == nil {
c.Verifier = &LocalVerifier{
Config: c,
Database: credential.NewLocalDatabase(),
}
}
}
for _, n := range cc.ServerNames {
switch n.Type {
case name.DNSNameType:
if n.Name != "" {
c.DNSComputerName = n.Name
}
if n.Domain != "" {
c.DNSDomainName = n.Domain
}
if n.Forest != "" {
c.DNSForestName = n.Forest
}
case name.NetBIOSNameType:
if n.Name != "" {
c.NetBIOSComputerName = n.Name
}
if n.Domain != "" {
c.NetBIOSDomainName = n.Domain
}
}
}
if cc.ChannelBindings != nil {
if c.ChannelBindings, err = cc.ChannelBindings.Marshal(); err != nil {
return nil, gssapi.ContextError(ctx, gssapi.BadBindings, gssapi.ErrBadBindings)
}
}
if cc.Capabilities.IsSet(gssapi.MutualAuthn) {
return nil, gssapi.ContextError(ctx, gssapi.Unavailable, ErrMutualAuthnNotSupported)
}
@@ -96,11 +183,6 @@ func (Mechanism) New(ctx context.Context) (gssapi.Mechanism, error) {
if cc.TargetNameFromUntrustedSource {
c.UnverifiedTargetName = cc.TargetNameFromUntrustedSource
}
if cc.ChannelBindings != nil {
if c.ChannelBindings, err = cc.ChannelBindings.Marshal(); err != nil {
return nil, gssapi.ContextError(ctx, gssapi.BadBindings, gssapi.ErrBadBindings)
}
}
return &Mechanism{
Authentifier: &Authentifier{Config: c},
}, nil
@@ -139,9 +221,30 @@ func (m *Mechanism) Init(ctx context.Context, tok *gssapi.Token) (*gssapi.Token,
return &gssapi.Token{Payload: b}, gssapi.ContextContinueNeeded(ctx)
}
func (m *Mechanism) Capabilities(ctx context.Context) gssapi.Cap {
return m.Config.Capabilities()
}
// The security context accept call.
func (m *Mechanism) Accept(ctx context.Context, tok *gssapi.Token) (*gssapi.Token, error) {
return nil, gssapi.ContextError(ctx, gssapi.Unavailable, gssapi.ErrUnavailable)
if m.session != nil {
err := m.VerifyAuthenticate(ctx, tok.Payload)
if err != nil {
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
gssapi.SetAttribute(ctx, gssapi.AttributeSessionKey, m.SessionKey())
return &gssapi.Token{}, gssapi.ContextComplete(ctx)
}
b, err := m.Challenge(ctx, tok.Payload)
if err != nil {
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
return &gssapi.Token{Payload: b}, gssapi.ContextContinueNeeded(ctx)
}
// The maximum message size for the given limit. (and flag determining if
@@ -285,6 +388,14 @@ func (m *Mechanism) Unwrap(ctx context.Context, tok *gssapi.MessageToken) (*gssa
tok.Capabilities &= ^gssapi.Confidentiality
}
if len(tok.Signature) == 0 {
if len(tok.Payload) < SignatureSize {
return nil, gssapi.ContextError(ctx, gssapi.DefectiveToken, gssapi.ErrDefectiveToken)
}
// compute signature and checksum from the payload if signature is not provided.
tok.Signature, tok.Payload = tok.Payload[:SignatureSize], tok.Payload[SignatureSize:]
}
// decrypt the payload.
if tok.Capabilities.IsSet(gssapi.Confidentiality) {
if err := m.ApplyInboundCipher(ctx, tok.Payload); err != nil {
@@ -369,6 +480,13 @@ func (m *Mechanism) MakeSignatureEx(ctx context.Context, tokEx *gssapi.MessageTo
// VerifySignature token.
func (m *Mechanism) VerifySignature(ctx context.Context, tok *gssapi.MessageToken) error {
if len(tok.Signature) == 0 {
if len(tok.Payload) < 16 {
return gssapi.ContextError(ctx, gssapi.DefectiveToken, gssapi.ErrDefectiveToken)
}
tok.Payload, tok.Signature = tok.Payload[:len(tok.Payload)-16], tok.Payload[len(tok.Payload)-16:]
}
expChk, err := m.MakeInboundChecksum(ctx, [][]byte{tok.Payload})
if err != nil {
return gssapi.ContextError(ctx, gssapi.Failure, err)
+5 -2
View File
@@ -37,6 +37,9 @@ type NTLMVersion interface {
// ChallengeResponse function computes the response for the server challenge message
// based on client challenge `nonce` parameter and set of negotiated flags.
ChallengeResponse(context.Context, Credential, *ChallengeMessage, []byte) (*ChallengeResponse, error)
// AuthenticateResponse function computes the response for the server authenticate message
// based on the server challenge `nonce` parameter.
AuthenticateResponse(context.Context, Credential, *AuthenticateMessage, []byte) (*ChallengeResponse, error)
// KeyExchangeKey function returns the key used to protect the session key that is
// generated by the client.
KeyExchangeKey(context.Context, *ChallengeMessage, *ChallengeResponse) ([]byte, error)
@@ -780,9 +783,9 @@ func (m *AuthenticateMessage) Unmarshal(ctx context.Context, b []byte) error {
e.ReadData(ctx, &m.Version)
// mic. (decode nt_challenge_response av_pairs).
if m.NTChallengeResponseLen > 24 { // at position 24 av_pairs start.
if m.NTChallengeResponseLen >= 44 {
attrs := AttrValues{}
if err := (&attrs).Unmarshal(ctx, b[m.NTChallengeResponseOffset+24:]); err != nil {
if err := (&attrs).Unmarshal(ctx, b[m.NTChallengeResponseOffset+44:]); err != nil {
return err
}
if attrs.Flag().IsSet(MICProvided) {
+2
View File
@@ -12,6 +12,8 @@ type Signature struct {
SeqNum uint32
}
const SignatureSize = 16
func (s *Signature) Marshal(ctx context.Context) ([]byte, error) {
e := internal.NewCodec(ctx, nil)
+62 -4
View File
@@ -146,7 +146,7 @@ func (v1 *V1) ChallengeResponse(ctx context.Context, cred Credential,
respNT, respLM := new(NTLMv1Response), new(LMv1Response)
if c.Negotiate.IsSet(NegotiateExtendedSessionSecurity) {
if v1.ExtendedSessionSecurity || c.Negotiate.IsSet(NegotiateExtendedSessionSecurity) {
if respNT.Response, err = crypto.MD5(c.ServerChallenge, nonce); err != nil {
return nil, fmt.Errorf("v1: compute response: %w", err)
}
@@ -161,6 +161,10 @@ func (v1 *V1) ChallengeResponse(ctx context.Context, cred Credential,
}
}
if resp.SessionBaseKey, err = crypto.MD4(resp.KeyNT); err != nil {
return nil, fmt.Errorf("v1: compute session base key: %v", err)
}
if resp.NT, err = respNT.Marshal(ctx); err != nil {
return nil, fmt.Errorf("v1: marshal nt response: %v", err)
}
@@ -169,10 +173,64 @@ func (v1 *V1) ChallengeResponse(ctx context.Context, cred Credential,
return nil, fmt.Errorf("v1: marshal lm response: %v", err)
}
return resp, nil
}
func (v1 *V1) AuthenticateResponse(ctx context.Context, cred Credential, a *AuthenticateMessage, nonce []byte) (*ChallengeResponse, error) {
if len(a.NTChallengeResponse) == 0 && bytes.Equal(a.LMChallengeResponse, []byte{0}) {
return &ChallengeResponse{
LM: []byte{0},
IsAnonymous: true,
SessionBaseKey: make([]byte, 16),
}, nil
}
var (
resp = new(ChallengeResponse)
err error
)
if resp.KeyNT, err = v1.NTOWF(ctx, cred); err != nil {
return nil, err
}
if resp.KeyLM, err = v1.LMOWF(ctx, cred); err != nil {
return nil, err
}
if v1.ExtendedSessionSecurity || a.Negotiate.IsSet(NegotiateExtendedSessionSecurity) {
if resp.NT, err = crypto.MD5(nonce, a.LMChallengeResponse); err != nil {
return nil, fmt.Errorf("v1: compute response: %w", err)
}
resp.NT = crypto.DESL(resp.KeyNT, resp.NT[:8])
resp.LM = a.LMChallengeResponse
} else {
resp.NT = crypto.DESL(resp.KeyNT, nonce)
resp.LM = crypto.DESL(resp.KeyLM, nonce)
}
if resp.SessionBaseKey, err = crypto.MD4(resp.KeyNT); err != nil {
return nil, fmt.Errorf("v1: compute session base key: %v", err)
}
if !bytes.Equal(a.NTChallengeResponse, resp.NT) {
return nil, fmt.Errorf("v1: authenticate response: nt challenge response mismatch")
}
if len(a.LMChallengeResponse) > 0 {
if !bytes.Equal(a.LMChallengeResponse, resp.LM) && !bytes.Equal(a.LMChallengeResponse, resp.NT) {
return nil, fmt.Errorf("v1: authenticate response: lm challenge response mismatch")
}
}
if resp.KeyExchangeKey, err = v1.KeyExchangeKey(ctx, &ChallengeMessage{
Negotiate: a.Negotiate,
ServerChallenge: v1.ServerChallenge,
}, resp); err != nil {
return nil, fmt.Errorf("v1: compute key exchange key: %v", err)
}
return resp, nil
}
@@ -188,7 +246,7 @@ func (v1 *V1) KeyExchangeKey(ctx context.Context, c *ChallengeMessage, chal *Cha
return nil, errors.New("v1: key exchange key: challenge response lm is empty")
}
if c.Negotiate.IsSet(NegotiateExtendedSessionSecurity) {
if v1.ExtendedSessionSecurity || c.Negotiate.IsSet(NegotiateExtendedSessionSecurity) {
return crypto.HMACMD5(chal.SessionBaseKey, c.ServerChallenge, chal.LM[:8])
}
@@ -196,7 +254,7 @@ func (v1 *V1) KeyExchangeKey(ctx context.Context, c *ChallengeMessage, chal *Cha
return nil, errors.New("v1: key exchange key: challenge response key lm is empty")
}
if c.Negotiate.IsSet(NegotiateLMKey) {
if v1.SecurityParameters.UseLMKey || c.Negotiate.IsSet(NegotiateLMKey) {
buf := bytes.NewBuffer(nil)
@@ -212,7 +270,7 @@ func (v1 *V1) KeyExchangeKey(ctx context.Context, c *ChallengeMessage, chal *Cha
return buf.Bytes(), nil
}
if c.Negotiate.IsSet(RequestNonNTSessionKey) {
if v1.SecurityParameters.NonNTSessionKey || c.Negotiate.IsSet(RequestNonNTSessionKey) {
return append(chal.KeyLM[:8], bytes.Repeat([]byte{0}, 8)...), nil
}
+81
View File
@@ -1,6 +1,7 @@
package ntlm
import (
"bytes"
"context"
"errors"
"fmt"
@@ -344,6 +345,86 @@ func (v2 *V2) ChallengeResponse(ctx context.Context, cred Credential, c *Challen
return resp, nil
}
func (v2 *V2) AuthenticateResponse(ctx context.Context, cred Credential, a *AuthenticateMessage, nonce []byte) (*ChallengeResponse, error) {
var (
resp = new(ChallengeResponse)
err error
)
if len(a.NTChallengeResponse) == 0 && bytes.Equal(a.LMChallengeResponse, []byte{0}) {
return &ChallengeResponse{
LM: []byte{0},
IsAnonymous: true,
SessionBaseKey: make([]byte, 16),
}, nil
}
// compute nt key.
if resp.KeyNT, err = v2.NTOWF(ctx, cred); err != nil {
return nil, err
}
// "compute" lm key.
resp.KeyLM = resp.KeyNT
respNT, respLM := new(NTLMv2Response), new(LMv2Response)
if err := respNT.Unmarshal(ctx, a.NTChallengeResponse); err != nil {
return nil, fmt.Errorf("v2: unmarshal nt challenge response: %w", err)
}
resp.Tmp = a.NTChallengeResponse[16:]
if resp.NT, err = crypto.HMACMD5(resp.KeyNT, nonce, resp.Tmp); err != nil {
return nil, fmt.Errorf("v2: compute response nt: %v", err)
}
if resp.SessionBaseKey, err = crypto.HMACMD5(resp.KeyNT, resp.NT); err != nil {
return nil, fmt.Errorf("v2: compute session base key: %v", err)
}
resp.NT = append(resp.NT, resp.Tmp...)
if !isZero(a.LMChallengeResponse) {
if err := respLM.Unmarshal(ctx, a.LMChallengeResponse); err != nil {
return nil, fmt.Errorf("v2: unmarshal lm challenge response: %w", err)
}
if resp.LM, err = crypto.HMACMD5(resp.KeyLM, nonce, respNT.NTLMv2ClientChallenge.ChallengeFromClient); err != nil {
return nil, fmt.Errorf("v2: compute response lm: %v", err)
}
resp.LM = append(resp.LM, respNT.NTLMv2ClientChallenge.ChallengeFromClient...)
}
if !bytes.Equal(a.NTChallengeResponse, resp.NT) {
return nil, fmt.Errorf("v2: authenticate response: nt challenge response mismatch")
}
if !isZero(a.LMChallengeResponse) {
if !bytes.Equal(a.LMChallengeResponse, resp.LM) {
return nil, fmt.Errorf("v2: authenticate response: lm challenge response mismatch")
}
}
if resp.KeyExchangeKey, err = v2.KeyExchangeKey(ctx, &ChallengeMessage{
Negotiate: a.Negotiate,
ServerChallenge: v2.ServerChallenge,
}, resp); err != nil {
return nil, fmt.Errorf("v2: compute key exchange key: %w", err)
}
return resp, nil
}
func isZero(b []byte) bool {
for _, v := range b {
if v != 0 {
return false
}
}
return true
}
// KeyExchangeKey function returns the key used to protect the session key that is
// generated by the client.
func (v2 *V2) KeyExchangeKey(ctx context.Context, c *ChallengeMessage, chal *ChallengeResponse) ([]byte, error) {
+54
View File
@@ -0,0 +1,54 @@
package ntlm
import (
"context"
"fmt"
"github.com/oiweiwei/go-msrpc/ssp/credential"
)
type Verifier interface {
VerifyChallenge(context.Context, *SecurityParameters, Credential, []byte, *AuthenticateMessage) (*ChallengeResponse, error)
}
type LocalVerifier struct {
Config *Config
Database credential.Database
}
func (v *LocalVerifier) Lookup(ctx context.Context, cred Credential) (Credential, bool) {
if credential.IsAnonymous(cred) && v.Config.AllowAnonymous {
return credential.Anonymous(), true
}
return v.Database.Lookup(ctx, cred)
}
func (v *LocalVerifier) VerifyChallenge(ctx context.Context, session *SecurityParameters, cred Credential, nonce []byte, am *AuthenticateMessage) (*ChallengeResponse, error) {
var (
ntlm NTLMVersion
)
ntlm = &V1{Config: v.Config, SecurityParameters: session}
if len(am.NTChallengeResponse) > 24 {
ntlm = &V2{Config: v.Config, SecurityParameters: session}
}
cred, ok := v.Lookup(ctx, cred)
if !ok {
return nil, fmt.Errorf("ntlm: local verifyer: credential not found for %s", cred.DomainName()+"\\"+cred.UserName())
}
resp, err := ntlm.AuthenticateResponse(ctx, cred, am, nonce)
if err != nil {
return nil, fmt.Errorf("ntlm: local provider: authenticate verification failed: %w", err)
}
resp.KeyExchangeKey, err = ntlm.KeyExchangeKey(ctx, &ChallengeMessage{ServerChallenge: nonce}, resp)
if err != nil {
return nil, fmt.Errorf("ntlm: local provider: key exchange key failed: %w", err)
}
return resp, nil
}
+194 -15
View File
@@ -9,6 +9,7 @@ import (
)
type Config struct {
IsServer bool
// The services available.
Capabilities gssapi.Cap
// The list of negotiated mechanisms.
@@ -55,6 +56,180 @@ func (a *Authentifier) Negotiate(ctx context.Context) ([]byte, error) {
return b, nil
}
func (a *Authentifier) Reject(ctx context.Context) ([]byte, error) {
resp := &NegTokenResp{
State: Reject,
}
b, err := resp.Marshal(ctx)
if err != nil {
return nil, fmt.Errorf("spnego: reject: marshal neg token resp: %w", err)
}
return b, nil
}
const Init2HintName = "not_defined_in_RFC4178@please_ignore"
func (a *Authentifier) ServerRespond(ctx context.Context, b []byte) ([]byte, error) {
var err error
if len(b) == 0 {
init2 := &NegTokenInit{
MechTypes: a.MakeMechanismList(ctx),
HintName: Init2HintName,
}
a.RetrievedMechanismList = init2.MechTypes
if a.Mechanism, err = a.SelectMechanism(ctx); err != nil {
return nil, err
}
if a.Mechanism == nil {
return nil, fmt.Errorf("spnego: init: no mechanism selected")
}
tok, err := a.Mechanism.Accept(ctx, &gssapi.Token{})
if err != nil {
return nil, fmt.Errorf("spnego: init: mechanism accept: %w", err)
}
init2.MechToken = tok.Payload
b, err := init2.Marshal(ctx)
if err != nil {
return nil, fmt.Errorf("spnego: init: marshal neg token init: %w", err)
}
return b, nil
}
var resp = new(NegTokenResp)
if a.IsNegTokenInit(ctx, b) {
init := &NegTokenInit{}
if err := init.Unmarshal(ctx, b); err != nil {
return nil, fmt.Errorf("spnego: init: unmarshal neg token init: %w", err)
}
a.RetrievedMechanismList = init.MechTypes
if a.Mechanism, err = a.SelectMechanism(ctx); err != nil {
return nil, fmt.Errorf("spnego: init: select mechanism: %w", err)
}
if a.Mechanism == nil {
return nil, fmt.Errorf("spnego: init: no mechanism selected")
}
tok, err := a.Mechanism.Accept(ctx, &gssapi.Token{Payload: init.MechToken})
if err != nil {
return nil, fmt.Errorf("spnego: init: mechanism accept: %w", err)
}
resp = &NegTokenResp{
SupportedMech: (asn1.ObjectIdentifier)(a.Mechanism.Type()),
ResponseToken: tok.Payload,
State: AcceptIncomplete,
}
if gssapi.IsComplete(ctx) {
// create mechanism list mic.
sgn, err := a.ComputeSignature(ctx, init.MechTokenMIC)
if err != nil {
return nil, fmt.Errorf("spnego: init: compute mech list mic: %w", err)
}
resp.MechListMIC = sgn
resp.State = AcceptCompleted
}
b, err := resp.Marshal(ctx)
if err != nil {
return nil, fmt.Errorf("spnego: init: marshal neg token resp: %w", err)
}
return b, nil
}
if err := resp.Unmarshal(ctx, b); err != nil {
return nil, fmt.Errorf("spnego: init: unmarshal neg token resp: %w", err)
}
mechTok, err := a.Mechanism.Accept(ctx, &gssapi.Token{Payload: resp.ResponseToken})
if err != nil {
return nil, fmt.Errorf("spnego: init: mechanism accept: %w", err)
}
micTok := resp.MechListMIC
resp = &NegTokenResp{
SupportedMech: (asn1.ObjectIdentifier)(a.Mechanism.Type()),
ResponseToken: mechTok.Payload,
State: AcceptIncomplete,
}
if a.Config.RequireMechanismListMIC {
resp.State = RequestMIC
}
if gssapi.IsComplete(ctx) {
if len(micTok) == 0 && a.Config.RequireMechanismListMIC {
return nil, fmt.Errorf("spnego: init: mechanism list mic required but not provided")
}
// verify the mechanism list mic.
resp.MechListMIC, err = a.ComputeSignature(ctx, micTok)
if err != nil {
return nil, fmt.Errorf("spnego: init: compute mech list mic: %w", err)
}
resp.State = AcceptCompleted
}
b, err = resp.Marshal(ctx)
if err != nil {
return nil, fmt.Errorf("spnego: init: marshal neg token resp: %w", err)
}
return b, nil
}
func (a *Authentifier) ComputeSignature(ctx context.Context, tok []byte) ([]byte, error) {
// create mechanism list mic.
b, err := asn1.Marshal(a.MakeMechanismList(ctx))
if err != nil {
return nil, fmt.Errorf("spnego: init: marshal mech list: %w", err)
}
if len(tok) > 0 {
if err := a.Mechanism.VerifySignature(ctx, &gssapi.MessageToken{Payload: b, Signature: tok}); err != nil {
return nil, fmt.Errorf("spnego: init: verify mech list mic: %w", err)
}
}
sgn, err := a.Mechanism.MakeSignature(ctx, &gssapi.MessageToken{Payload: b})
if err != nil {
return nil, fmt.Errorf("spnego: init: make mech list signature: %w", err)
}
rst, ok := (any)(a.Mechanism).(interface{ ResetSecurityService(context.Context) error })
if ok {
if err := rst.ResetSecurityService(ctx); err != nil {
return nil, fmt.Errorf("spnego: init: reset security service: %w", err)
}
}
return sgn.Signature, nil
}
func (a *Authentifier) Respond(ctx context.Context, b []byte) ([]byte, error) {
var err error
@@ -71,17 +246,8 @@ func (a *Authentifier) Respond(ctx context.Context, b []byte) ([]byte, error) {
a.RetrievedMechanismList = init.MechTypes
loop:
// select the mechanism from the retrieved list.
for i := range a.MechanismsList {
for _, rm := range a.RetrievedMechanismList {
if a.MechanismsList[i].Type().Equal((gssapi.OID)(rm)) {
if a.Mechanism, err = a.MechanismsList[i].New(ctx); err != nil {
continue
}
break loop
}
}
if a.Mechanism, err = a.SelectMechanism(ctx); err != nil {
return nil, gssapi.ErrFailure
}
if a.Mechanism == nil {
@@ -169,6 +335,7 @@ func (a *Authentifier) Respond(ctx context.Context, b []byte) ([]byte, error) {
return nil, fmt.Errorf("spnego: init: reset security service: %w", err)
}
}
}
b, err = resp.Marshal(ctx)
@@ -180,7 +347,7 @@ func (a *Authentifier) Respond(ctx context.Context, b []byte) ([]byte, error) {
}
func (a *Authentifier) IsNegTokenInit(ctx context.Context, b []byte) bool {
return len(b) > 0 && b[0] == Application|0
return len(b) > 0 && b[0] == 0x60 // Application|0
}
func (a *Authentifier) MakeMechanismList(ctx context.Context) []asn1.ObjectIdentifier {
@@ -198,9 +365,21 @@ func (a *Authentifier) MakeMechanismList(ctx context.Context) []asn1.ObjectIdent
return mechTypes
}
func (a *Authentifier) SelectMechanism(ctx context.Context, oid gssapi.OID) gssapi.Mechanism {
func (a *Authentifier) SelectMechanism(ctx context.Context) (gssapi.Mechanism, error) {
// select mechanism based on oid or first entry if default...
return nil
var err error
// select the mechanism from the retrieved list.
for i := range a.MechanismsList {
for _, rm := range a.RetrievedMechanismList {
if a.MechanismsList[i].Type().Equal((gssapi.OID)(rm)) {
if a.Mechanism, err = a.MechanismsList[i].New(ctx); err != nil {
continue
}
return a.Mechanism, nil
}
}
}
return nil, err
}
+19 -1
View File
@@ -63,6 +63,7 @@ func (Mechanism) New(ctx context.Context) (gssapi.Mechanism, error) {
// set capabilities.
c.Capabilities = cc.Capabilities
c.IsServer = cc.IsServer
// check that mechanism list is not empty.
if len(c.MechanismsList) == 0 {
@@ -110,8 +111,25 @@ func (m *Mechanism) Init(ctx context.Context, tok *gssapi.Token) (*gssapi.Token,
}
func (m *Mechanism) Capabilities(ctx context.Context) gssapi.Cap {
return m.Mechanism.Capabilities(ctx)
}
func (m *Mechanism) Accept(ctx context.Context, tok *gssapi.Token) (*gssapi.Token, error) {
return nil, nil
b, err := m.ServerRespond(ctx, tok.Payload)
if err != nil {
b, err1 := m.Reject(ctx)
if err1 != nil {
return nil, gssapi.ContextError(ctx, gssapi.Failure, err)
}
return &gssapi.Token{Payload: b}, gssapi.ContextError(ctx, gssapi.Failure, err)
}
return &gssapi.Token{
Payload: b,
}, nil
}
func (m *Mechanism) WrapSizeLimit(ctx context.Context, sz int, conf bool) int {