mirror of
https://github.com/oiweiwei/go-msrpc
synced 2026-06-08 16:28:58 +00:00
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:
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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_
|
||||
@@ -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...))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user