Generated from Manticore v1.1.6 • 388 packages. View on pkg.go.dev

spnego

import "github.com/TheManticoreProject/Manticore/crypto/spnego"

Index

Constants

GSS API constants

const (
    GSS_API_SPNEGO = 0x60 // [APPLICATION 0]
)

Variables

Verification outcomes reported by AcceptContext.Verify. They are distinguished because a caller acts differently on each: a capture-only acceptor expects ErrCaptureOnly on every exchange, whereas a serving acceptor treats an unknown identity and a wrong password the same way on the wire but wants them apart in its logs.

var (
    // ErrCaptureOnly reports that the AUTHENTICATE was recorded but not verified,
    // because no CredentialLookup is configured. The captured material is
    // available regardless; this is the expected outcome for an acceptor whose
    // purpose is to harvest responses rather than to serve.
    ErrCaptureOnly = errors.New("spnego: authenticate recorded but not verified, no credential lookup configured")

    // ErrNoAuthenticate reports that Verify was called before an AUTHENTICATE was
    // accepted.
    ErrNoAuthenticate = errors.New("spnego: no AUTHENTICATE message has been accepted")

    // ErrUnknownIdentity reports that CredentialLookup had no credential for the
    // identity the client claimed.
    ErrUnknownIdentity = errors.New("spnego: no credential for the claimed identity")

    // ErrBadResponse reports that the NT challenge response did not verify
    // against the credential, which is what a wrong password looks like.
    ErrBadResponse = errors.New("spnego: NT challenge response did not verify")

    // ErrBadMIC reports that the NT challenge response verified but the message
    // integrity code did not, meaning the exchange was tampered with in flight.
    ErrBadMIC = errors.New("spnego: AUTHENTICATE message integrity code did not verify")
)

OIDs for various authentication mechanisms

var (
    // SPNEGO OID: 1.3.6.1.5.5.2
    // iso.org.dod.internet.security.mechanism.snego
    // Source: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-spng/94ccc4f8-d224-495f-8d31-4f58d1af598e
    SpnegoOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2}

    // SPNegoEx OID: 1.3.6.1.4.1.311.2.2.30
    // iso.org.dod.internet.private.enterprise.Microsoft.security.mechanisms.SPNegoEx
    // Source: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-spng/f5edf48c-57cc-4c61-bff9-ee19b9cd059e
    SPNegoExOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 2, 2, 30}

    // NTLM OID: 1.3.6.1.4.1.311.2.2.10
    // iso.org.dod.internet.private.enterprise.Microsoft.security.mechanisms.NTLM
    // Source: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/e21c0b07-8662-41b7-8853-2b9184eab0db
    NtlmOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 2, 2, 10}

    // Kerberos OID: 1.2.840.113554.1.2.2
    // iso.org.dod.internet.private.enterprise.Microsoft.security.mechanisms.Kerberos
    // Source: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-spng/211417c4-11ef-46c0-a8fb-f178a51c2088
    KerberosOID = asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}

    // MSKerberosOID: 1.2.840.48018.1.2.2 — Microsoft's legacy "MS KRB5" mechanism
    // OID. Windows advertises this in the SPNEGO mechTypes for Kerberos, and it is
    // what Windows RPC clients send, so it is used for the RPC Negotiate bind.
    MSKerberosOID = asn1.ObjectIdentifier{1, 2, 840, 48018, 1, 2, 2}
)

func CreateNegTokenInit

func CreateNegTokenInit(ntlmToken []byte) ([]byte, error)

CreateNegTokenInit creates a SPNEGO NegTokenInit with the given NTLM token and marshals it. Parameters:

  • ntlmToken: The NTLM token bytes to include in the SPNEGO token

Returns:

  • []byte: The marshaled SPNEGO token containing the NTLM token
  • error: An error if token creation fails

func CreateNegTokenInitKerberos

func CreateNegTokenInitKerberos(kerberosToken []byte) ([]byte, error)

CreateNegTokenInitKerberos creates a SPNEGO NegTokenInit advertising the Kerberos mechanism and carrying the given Kerberos GSS-API token (a KRB_AP_REQ) as the mechToken.

func CreateNegTokenResp

func CreateNegTokenResp(state NegState, mech asn1.ObjectIdentifier, token []byte) ([]byte, error)

CreateNegTokenResp creates an ASN.1 encoded SPNEGO NegTokenResp Parameters:

  • state: The negotiation state
  • mech: The supported mechanism OID
  • token: The response token bytes

Returns:

  • []byte: The encoded SPNEGO token
  • error: An error if token creation fails

func ExtractNTLMToken

func ExtractNTLMToken(spnegoToken []byte) ([]byte, error)

ExtractNTLMToken extracts the NTLM token from a SPNEGO token (init or resp)

func OIDtoString

func OIDtoString(oid asn1.ObjectIdentifier) string

OIDtoString converts an OID to a human-readable string representation

func PrepareSessionSetupRequest

func PrepareSessionSetupRequest(token []byte, useUnicode bool) []byte

PrepareSessionSetupRequest prepares the SMB session setup request with SPNEGO token Parameters:

  • token: The SPNEGO token bytes to prepare
  • useUnicode: Whether to encode the token in UTF-16LE

Returns:

  • []byte: The prepared token, encoded in UTF-16LE if useUnicode is true

type AcceptContext

AcceptContext is the acceptor side of an NTLM authentication carried over SPNEGO: the counterpart of AuthContext, which initiates one.

The exchange is two calls. AcceptNegotiateToken consumes the client’s NEGOTIATE and returns the CHALLENGE to send back. AcceptAuthenticateToken consumes the client’s AUTHENTICATE and records it. Verification is a separate step, so an acceptor that only wants to harvest responses never has to hold a credential, while one that serves calls Verify to decide.

A context handles one exchange and is not safe for concurrent use.

type AcceptContext struct {
    // TargetName is the name advertised in the CHALLENGE. Leaving it empty omits
    // it, which a client is entitled to refuse.
    TargetName string

    // TargetType declares whether TargetName names a domain or a server.
    TargetType challenge.TargetType

    // TargetInfo is the AV_PAIR list advertised in the CHALLENGE, as built by
    // targetinfo.BuildServerTargetInfo. A client folds these bytes into its
    // NTLMv2 blob, so they are covered by the response and cannot be changed
    // after the CHALLENGE is sent.
    //
    // Supplying an MsvAvTimestamp here obliges the client to carry a MIC, which
    // Verify then checks; omit it if the acceptor would rather not require one.
    TargetInfo []byte

    // ServerChallenge is the 8-byte nonce. The zero value means generate one from
    // the system's cryptographic random source when the CHALLENGE is built, which
    // is what an acceptor should normally do: a predictable or reused challenge
    // lets a captured response be replayed.
    ServerChallenge [8]byte

    // Version is advertised in the CHALLENGE. Nil omits it.
    Version *version.Version

    // CredentialLookup resolves a claimed identity to the account's NT hash.
    // Returning false means the identity is unknown.
    //
    // Nil makes the acceptor capture-only: Verify records nothing further and
    // reports ErrCaptureOnly. Holding NT hashes rather than passwords is
    // deliberate — the hash is all that verification needs.
    CredentialLookup func(domain, username string) (ntHash [16]byte, ok bool)

    // Negotiate and NegotiateMessageBytes hold the client's NEGOTIATE, the
    // latter because the MIC is computed over the raw message.
    Negotiate             *negotiate.NegotiateMessage
    NegotiateMessageBytes []byte

    // Challenge and ChallengeMessageBytes hold the CHALLENGE that was issued,
    // for the same reason.
    Challenge             *challenge.ChallengeMessage
    ChallengeMessageBytes []byte

    // Authenticate and AuthenticateMessageBytes hold the client's AUTHENTICATE.
    Authenticate             *authenticate.AuthenticateMessage
    AuthenticateMessageBytes []byte

    // SessionKey is the ExportedSessionKey, set by a successful Verify. It is the
    // key a consumer uses for message signing and sealing. It stays nil until
    // verification succeeds, because deriving it requires the credential.
    SessionKey []byte

    // Verified records whether Verify has succeeded.
    Verified bool
}

func NewAcceptContext

func NewAcceptContext(targetName string, targetType challenge.TargetType, targetInfo []byte) *AcceptContext

NewAcceptContext creates an acceptor advertising the given identity.

Parameters:

  • targetName: the name to advertise in the CHALLENGE
  • targetType: whether targetName names a domain or a server
  • targetInfo: the AV_PAIR list to advertise, or nil

Returns:

  • The acceptor context

func (*AcceptContext) AcceptAuthenticateToken

func (ctx *AcceptContext) AcceptAuthenticateToken(token []byte) error

AcceptAuthenticateToken consumes the client’s AUTHENTICATE token and records it, without verifying anything.

Recording and verifying are separate so that a failure to authenticate does not lose the response: the material is captured whether or not a credential exists to check it against. Call Verify afterwards to decide whether to honour it.

Parameters:

  • token: the client’s AUTHENTICATE, as a SPNEGO token or a bare NTLMSSP message

Returns:

  • An error if the token cannot be parsed

func (*AcceptContext) AcceptNegotiateToken

func (ctx *AcceptContext) AcceptNegotiateToken(token []byte) ([]byte, error)

AcceptNegotiateToken consumes the client’s NEGOTIATE token and returns the SPNEGO token carrying the CHALLENGE to send back.

The returned bytes are framed exactly as CreateAuthenticateTokenFromChallengeToken expects to receive them: a SecurityBlob wrapping a NegTokenResp whose negState is accept-incomplete and whose supportedMech is the NTLM OID.

Parameters:

  • token: the client’s NEGOTIATE, as a SPNEGO token or a bare NTLMSSP message

Returns:

  • The SPNEGO token carrying the CHALLENGE
  • An error if the client’s token cannot be answered

func (*AcceptContext) CapturedResponse

func (ctx *AcceptContext) CapturedResponse() (*ntlmv1.NTLMv1Response, *ntlmv2.NTLMv2Response)

CapturedResponse renders the recorded AUTHENTICATE as a crackable response.

Exactly one of the two is returned, chosen by the length of the NT challenge response: 24 bytes is an NTLMv1 response, anything longer is NTLMv2. A response that is neither yields two nils, as does a context with no AUTHENTICATE.

Returns:

  • The NTLMv1 response, or nil
  • The NTLMv2 response, or nil

func (*AcceptContext) CompletionToken

func (ctx *AcceptContext) CompletionToken() ([]byte, error)

CompletionToken builds the token that closes a SPNEGO exchange: a NegTokenResp reporting accept-completed, wrapped in a SecurityBlob.

This is not optional padding on a successful logon. RFC 4178 section 4.2.2 has the acceptor report the outcome of the negotiation in negState, and a mechanism that has finished is reported as accept-completed(0); an initiator whose state machine is still waiting for that token treats an empty final blob as a protocol violation and abandons the session, even though the server considered the logon successful. So the final leg carries this rather than nothing.

No responseToken accompanies it: NTLM’s last message is the client’s AUTHENTICATE, so there is nothing left for the server to send. The mechanism is named again so an initiator that tracks which mechanism was settled on can confirm it.

Returns:

  • The marshalled SecurityBlob
  • An error if it cannot be built

func (*AcceptContext) GetSessionKey

func (ctx *AcceptContext) GetSessionKey() []byte

GetSessionKey returns the ExportedSessionKey derived by a successful Verify, or nil if verification has not succeeded.

Returns:

  • The session key, or nil

func (*AcceptContext) Identity

func (ctx *AcceptContext) Identity() (domain, username, workstation string)

Identity reports the domain, username and workstation the client claimed in its AUTHENTICATE, decoded according to the character set the exchange negotiated.

The values are what the client asserted, not anything verified: they are meaningful for a capture record before Verify runs, and remain unvalidated input until it succeeds.

Returns:

  • The claimed domain, username and workstation, empty if no AUTHENTICATE has been accepted

func (*AcceptContext) Verify

func (ctx *AcceptContext) Verify() error

Verify checks the recorded AUTHENTICATE against the credential for the identity it claimed, and on success derives the ExportedSessionKey.

It reports ErrCaptureOnly when no CredentialLookup is configured, ErrUnknownIdentity when the lookup has no credential, ErrBadResponse when the NT response does not verify, and ErrBadMIC when the response verifies but the message integrity code does not.

Returns:

  • nil when the authentication is genuine, with SessionKey populated

type AuthContext

AuthContext holds the state for an authentication session

type AuthContext struct {
    Type        AuthType
    Domain      string
    Username    string
    Password    string
    Workstation string
    UseUnicode  bool

    // NTHash is the hex-encoded (32 hex chars) NT hash used for pass-the-hash. When set,
    // the NTLM AUTHENTICATE is computed from this hash instead of Password, so a caller
    // can authenticate with only the NT hash. Empty means authenticate with Password.
    NTHash string

    // NTLM specific fields
    NTLMChallenge *challenge.ChallengeMessage

    // NegotiateMessageBytes retains the raw NTLM NEGOTIATE_MESSAGE that was sent,
    // so the AUTHENTICATE MIC can be computed over NEGOTIATE||CHALLENGE||AUTHENTICATE.
    NegotiateMessageBytes []byte

    // SessionKey is the session key derived during the most recent successful
    // CreateAuthenticateTokenFromChallengeToken call. It is not transmitted on the
    // wire; callers use it as the MAC key for SMB message signing. It is nil until
    // authentication has produced a key (and for auth paths that do not derive one).
    SessionKey []byte

    // Kerberos supplies the GSS-API tokens when Type is AuthTypeKerberos. It is set
    // by the consumer (SMB/DCE-RPC) so crypto/spnego needs no dependency on the
    // Kerberos implementation (dependency inversion).
    Kerberos KerberosProvider
}

func NewAuthContext

func NewAuthContext(authType AuthType, domain, username, password, workstation string, useUnicode bool) *AuthContext

NewAuthContext creates a new authentication context Parameters:

  • authType: The type of authentication to use (NTLM or Kerberos)
  • domain: The domain name for authentication
  • username: The username to authenticate with
  • password: The password for authentication
  • workstation: The name of the client workstation
  • useUnicode: Whether to use Unicode encoding

Returns:

  • *AuthContext: A new authentication context initialized with the provided parameters

func (*AuthContext) CreateAuthenticateTokenFromChallengeToken

func (ctx *AuthContext) CreateAuthenticateTokenFromChallengeToken(challengeToken []byte) ([]byte, error)

CreateAuthenticateTokenFromChallengeToken processes the server’s challenge token and creates an authenticate token Parameters:

  • challengeToken: The SPNEGO token containing the server’s challenge

Returns:

  • []byte: The SPNEGO token containing the authenticate message
  • error: An error if token processing fails

func (*AuthContext) CreateNegotiateToken

func (ctx *AuthContext) CreateNegotiateToken(negotiateFlags flags.NegotiateFlags, version *version.Version) ([]byte, error)

CreateNegotiateToken creates the initial SPNEGO token with NTLM negotiate message Parameters:

  • ctx: The authentication context containing domain, username, password, and other settings

Returns:

  • []byte: The SPNEGO token containing the NTLM negotiate message
  • error: An error if token creation fails

func (*AuthContext) GetAuthType

func (ctx *AuthContext) GetAuthType() AuthType

GetAuthType returns the authentication type Returns:

  • AuthType: The authentication type (NTLM or Kerberos) for this context

func (*AuthContext) GetSessionKey

func (ctx *AuthContext) GetSessionKey() []byte

GetSessionKey returns the session key derived during authentication, or nil if none has been derived yet.

Returns:

  • []byte: The session key (MAC key for SMB signing), or nil

func (*AuthContext) ServerIdentity

func (ctx *AuthContext) ServerIdentity() (ServerIdentity, bool)

ServerIdentity extracts the server identity from the NTLM CHALLENGE processed during authentication. ok is false when no NTLM challenge has been processed yet (for example before CreateAuthenticateTokenFromChallengeToken is called, or for a non-NTLM exchange).

func (*AuthContext) SetAuthType

func (ctx *AuthContext) SetAuthType(authType AuthType)

SetAuthType sets the authentication type Parameters:

  • authType: The authentication type (NTLM or Kerberos) to set

func (*AuthContext) SupportsExtendedSessionSecurity

func (ctx *AuthContext) SupportsExtendedSessionSecurity() bool

SupportsExtendedSessionSecurity reports whether the server’s NTLM CHALLENGE advertised NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY — i.e. NTLM2 / NTLMv2 session security. It returns false when no NTLM challenge has been processed.

type AuthType

AuthType represents the authentication type

type AuthType int
const (
    AuthTypeNTLM AuthType = iota
    AuthTypeKerberos
)

type KerberosProvider

KerberosProvider produces and verifies the Kerberos GSS-API tokens exchanged inside SPNEGO. A consumer backs it with the native Kerberos stack and assigns it to AuthContext.Kerberos.

type KerberosProvider interface {
    // InitToken returns the initial GSS-API token (a KRB_AP_REQ) to carry in the
    // SPNEGO NegTokenInit mechToken.
    InitToken() ([]byte, error)
    // AcceptResponseToken verifies the server's response token (a KRB_AP_REP for
    // mutual authentication). An empty token is accepted as a no-op.
    AcceptResponseToken(token []byte) error
    // SessionKey returns the established context key used for message
    // signing/sealing after the exchange completes.
    SessionKey() []byte
}

type NegState

NegState represents the negotiation state in SPNEGO

type NegState asn1.Enumerated
const (
    // NegStateAcceptCompleted indicates the negotiation is complete and successful
    NegStateAcceptCompleted NegState = 0
    // NegStateAcceptIncomplete indicates more negotiation messages are needed
    NegStateAcceptIncomplete NegState = 1
    // NegStateReject indicates the negotiation has failed
    NegStateReject NegState = 2
    // NegStateRequestMIC indicates a MIC token is requested
    NegStateRequestMIC NegState = 3
)

func (NegState) String

func (n NegState) String() string

String returns a string representation of the NegState Parameters:

  • n: The NegState to convert to a string

Returns:

  • string: A string representation of the NegState

type NegTokenInit

NegTokenInit is the initial SPNEGO token sent by the client wrapped in a [0] EXPLICIT context tag.

type NegTokenInit struct {
    MechTypes    []asn1.ObjectIdentifier `asn1:"explicit,tag:0"`
    ReqFlags     asn1.BitString          `asn1:"explicit,optional,tag:1"`
    MechToken    []byte                  `asn1:"explicit,optional,tag:2"`
    MechTokenMIC []byte                  `asn1:"explicit,optional,tag:3"`
}

func NewNegTokenInit

func NewNegTokenInit(mechTypes []asn1.ObjectIdentifier, reqFlags asn1.BitString, mechToken []byte, mechTokenMIC []byte) *NegTokenInit

NewNegTokenInit creates a new NegTokenInit with the specified parameters Parameters:

  • mechTypes: The mechanism type identifiers to include
  • reqFlags: The requested flags for the token
  • mechToken: The mechanism token bytes
  • mechTokenMIC: The mechanism token MIC bytes

Returns:

  • *NegTokenInit: A new NegTokenInit initialized with the provided parameters

func (*NegTokenInit) Marshal

func (n *NegTokenInit) Marshal() ([]byte, error)

Marshal marshals the NegTokenInit into a byte slice Returns:

  • []byte: The marshaled SPNEGO token bytes
  • error: An error if marshaling fails

func (*NegTokenInit) SetMechToken

func (n *NegTokenInit) SetMechToken(mechToken []byte)

SetMechToken sets the mech token in the NegTokenInit Parameters:

  • mechToken: The mechanism token bytes to set

func (*NegTokenInit) SetMechTokenNTLM

func (n *NegTokenInit) SetMechTokenNTLM(mechToken []byte)

SetMechTokenNTLM sets the NTLM token in the NegTokenInit Parameters:

  • mechToken: The NTLM mechanism token bytes to set

func (*NegTokenInit) Unmarshal

func (n *NegTokenInit) Unmarshal(data []byte) error

Unmarshal unmarshals the NegTokenInit from a byte slice Parameters:

  • data: The bytes to unmarshal from

Returns:

  • error: An error if unmarshaling fails

type NegTokenResp

NegTokenResp is the response token sent by the server

NegTokenResp ::= SEQUENCE {
    negState[0]       ENUMERATED {
        accept-completed(0),
        accept-incomplete(1),
        reject(2),
        request-mic(3)
    } OPTIONAL,
    supportedMech[1]  MechType OPTIONAL,
    responseToken[2]  OCTET STRING OPTIONAL,
    mechListMIC[3]    OCTET STRING OPTIONAL
}
type NegTokenResp struct {
    NegState      NegState              `asn1:"optional,tag:0"`
    SupportedMech asn1.ObjectIdentifier `asn1:"optional,tag:1"`
    ResponseToken []byte                `asn1:"optional,tag:2,octet"`
    MechListMIC   []byte                `asn1:"optional,tag:3,octet"`

    // SuppressNegState omits the [0] negState field on Marshal. A client's
    // continuation token (the AUTHENTICATE) carries only the responseToken — no
    // negState and no supportedMech — matching the Windows client.
    SuppressNegState bool `asn1:"-"`
}

func NewNegTokenResp

func NewNegTokenResp(state NegState, mech asn1.ObjectIdentifier, responseToken []byte) *NegTokenResp

NewNegTokenResp creates a new NegTokenResp Parameters:

  • state: The negotiation state
  • mech: The supported mechanism OID
  • responseToken: The response token bytes

Returns:

  • *NegTokenResp: A new NegTokenResp instance

func (*NegTokenResp) Marshal

func (n *NegTokenResp) Marshal() ([]byte, error)

Marshal marshals the NegTokenResp into a byte slice Returns:

  • []byte: The marshaled bytes
  • error: An error if marshaling fails

func (*NegTokenResp) SetMechToken

func (n *NegTokenResp) SetMechToken(responseToken []byte)

SetMechToken sets the mech token in the NegTokenResp Parameters:

  • responseToken: The response token bytes to set

func (*NegTokenResp) SetMechTokenNTLM

func (n *NegTokenResp) SetMechTokenNTLM(responseToken []byte)

SetMechTokenNTLM sets the NTLM token in the NegTokenResp Parameters:

  • responseToken: The NTLM response token bytes to set

func (*NegTokenResp) Unmarshal

func (n *NegTokenResp) Unmarshal(data []byte) (int, error)

Unmarshal unmarshals the NegTokenResp from a byte slice Parameters:

  • data: The bytes to unmarshal

Returns:

  • int: The number of bytes read
  • error: An error if unmarshaling fails

type SecurityBlob

SecurityBlob represents a security token wrapped in ASN.1 encoding. It contains arbitrary security token data that is tagged with ASN.1 metadata.

type SecurityBlob struct {
    Data []byte `asn1:"explicit,tag:0,octet"`
}

func (*SecurityBlob) Marshal

func (s *SecurityBlob) Marshal() ([]byte, error)

Marshal encodes this SecurityBlob into ASN.1 format. The data is wrapped in a [1] ASN.1 tag before encoding.

Returns:

  • []byte: The ASN.1 encoded security blob
  • error: Any error encountered during marshaling

func (*SecurityBlob) Unmarshal

func (s *SecurityBlob) Unmarshal(marshalledData []byte) (int, error)

Unmarshal decodes an ASN.1 encoded security blob into this SecurityBlob struct. The data is expected to be wrapped in a [1] ASN.1 tag.

Parameters:

  • marshalledData: The ASN.1 encoded bytes to unmarshal

Returns:

  • int: Number of bytes read from marshalledData
  • error: Any error encountered during unmarshaling

type ServerIdentity

ServerIdentity is the server identity advertised in the NTLM CHALLENGE: the NetBIOS and DNS computer/domain names from the TargetInfo AV pairs, and the operating-system version from the CHALLENGE Version field. Individual fields are empty (or zero) when the server did not advertise them.

type ServerIdentity struct {
    NetBIOSComputerName string
    NetBIOSDomainName   string
    DNSComputerName     string
    DNSDomainName       string
    OSVersionMajor      uint8
    OSVersionMinor      uint8
    OSVersionBuild      uint16
}

Subpackages