Documentation
¶
Overview ¶
Package auth provides authentication for SMB protocol adapters.
This file implements the adapter.Authenticator interface for SMB, wrapping the existing NTLM and SPNEGO authentication mechanisms. The SMBAuthenticator bridges SPNEGO-wrapped NTLM/Kerberos tokens to DittoFS's identity model via the control plane UserStore.
Package auth provides authentication for SMB protocol adapters.
NTLM (NT LAN Manager) is a challenge-response authentication protocol defined in [MS-NLMP]. This file provides:
- NTLM message detection and parsing
- Challenge (Type 2) message building
- Support for guest/anonymous authentication
For production use with credential validation, additional implementation of NTLMv2 response verification is required.
Package auth provides authentication for SMB protocol adapters.
This file provides SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) parsing and building. It wraps github.com/jcmturner/gokrb5/v8/spnego to provide a clean interface for extracting mechanism tokens and building server responses.
SPNEGO is defined in RFC 4178 and is used by:
- SMB: Wraps NTLM or Kerberos tokens in SESSION_SETUP
- NFSv4: Used with RPCSEC_GSS for Kerberos authentication
Index ¶
- Variables
- func BuildAcceptComplete(mech asn1.ObjectIdentifier, responseToken []byte) ([]byte, error)
- func BuildAcceptIncomplete(mech asn1.ObjectIdentifier, responseToken []byte) ([]byte, error)
- func BuildChallenge() (message []byte, serverChallenge [8]byte)
- func BuildMinimalTargetInfo() []byte
- func BuildReject() ([]byte, error)
- func BuildResponse(state NegState, mech asn1.ObjectIdentifier, responseToken []byte) ([]byte, error)
- func ComputeNTHash(password string) [16]byte
- func ComputeNTLMv2Hash(ntHash [16]byte, username, domain string) [16]byte
- func DeriveSigningKey(sessionBaseKey [16]byte, flags NegotiateFlag, encryptedKey []byte) [16]byte
- func IsValid(buf []byte) bool
- func ValidateNTLMv2Response(ntHash [16]byte, username, domain string, serverChallenge [8]byte, ...) ([16]byte, error)
- type AuthenticateMessage
- type AvID
- type Error
- type MessageType
- type NegState
- type NegotiateFlag
- type ParsedToken
- type SMBAuthenticator
- type TokenType
Constants ¶
This section is empty.
Variables ¶
var ( // OIDMSKerberosV5 is Microsoft's Kerberos 5 OID (1.2.840.48018.1.2.2). // Used by Windows clients for Kerberos authentication. OIDMSKerberosV5 = asn1.ObjectIdentifier{1, 2, 840, 48018, 1, 2, 2} // OIDKerberosV5 is the standard Kerberos 5 OID (1.2.840.113554.1.2.2). // Defined in RFC 4121. OIDKerberosV5 = asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2} // OIDNTLMSSP is the NTLM Security Support Provider OID (1.3.6.1.4.1.311.2.2.10). // Used for NTLM authentication over SPNEGO. OIDNTLMSSP = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 2, 2, 10} // OIDSPNEGO is the SPNEGO mechanism OID (1.3.6.1.5.5.2). // Identifies the outer GSSAPI wrapper. OIDSPNEGO = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2} )
Well-known mechanism OIDs used in SPNEGO negotiation. These identify the authentication mechanism being used.
var ( ErrInvalidToken = errors.New("spnego: invalid token format") ErrUnsupportedMech = errors.New("spnego: unsupported mechanism") ErrNoMechToken = errors.New("spnego: no mechanism token present") ErrUnexpectedToken = errors.New("spnego: unexpected token type") )
Error types for SPNEGO parsing.
var Signature = []byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}
Signature is the 8-byte signature that identifies NTLM messages. All NTLM messages begin with this signature: "NTLMSSP\0" [MS-NLMP] Section 2.2.1
Functions ¶
func BuildAcceptComplete ¶
func BuildAcceptComplete(mech asn1.ObjectIdentifier, responseToken []byte) ([]byte, error)
BuildAcceptComplete creates a NegTokenResp indicating successful authentication. Used after validating the NTLM authenticate (Type 3) message.
func BuildAcceptIncomplete ¶
func BuildAcceptIncomplete(mech asn1.ObjectIdentifier, responseToken []byte) ([]byte, error)
BuildAcceptIncomplete creates a NegTokenResp indicating more tokens are needed. Used when sending the NTLM challenge (Type 2) message.
func BuildChallenge ¶
BuildChallenge creates an NTLM Type 2 (CHALLENGE) message.
Returns the challenge message and the 8-byte server challenge that was embedded in the message. The server challenge must be stored to validate the client's NTLMv2 response and derive the session key.
The returned message has the following structure:
Offset Size Field Value/Description ------ ---- ---------------- ---------------------------------- 0 8 Signature "NTLMSSP\0" 8 4 MessageType 2 (CHALLENGE) 12 8 TargetNameFields Empty target name (Len=0) 20 4 NegotiateFlags Server capabilities 24 8 ServerChallenge Random 8-byte challenge 32 8 Reserved Zero 40 8 TargetInfoFields Minimal AV_PAIR list 48 8 Version Zero (not populated) 56 var Payload TargetInfo terminator
[MS-NLMP] Section 2.2.1.2
func BuildMinimalTargetInfo ¶
func BuildMinimalTargetInfo() []byte
BuildMinimalTargetInfo creates a minimal AV_PAIR list with just the terminator. Useful for testing NTLM message parsing without full target info.
[MS-NLMP] Section 2.2.2.1
func BuildReject ¶
BuildReject creates a NegTokenResp indicating authentication failure.
func BuildResponse ¶
func BuildResponse(state NegState, mech asn1.ObjectIdentifier, responseToken []byte) ([]byte, error)
BuildResponse creates a SPNEGO NegTokenResp for server responses.
Parameters:
- state: The negotiation state (accept, reject, incomplete)
- mech: The selected mechanism OID (can be nil if rejecting)
- responseToken: The mechanism-specific response (e.g., NTLM challenge)
Returns the ASN.1 DER-encoded NegTokenResp.
func ComputeNTHash ¶
ComputeNTHash computes the NT hash from a password.
The NT hash is computed as: MD4(UTF16LE(password))
This is the fundamental credential used in NTLM authentication. The NT hash should be stored securely (it's equivalent to a password).
[MS-NLMP] Section 3.3.1
This function delegates to models.ComputeNTHash to avoid code duplication.
func ComputeNTLMv2Hash ¶
ComputeNTLMv2Hash computes the NTLMv2 response key.
The NTLMv2 hash is computed as:
HMAC-MD5(NT_Hash, UPPERCASE(username) + domain)
where username and domain are encoded as UTF-16LE.
[MS-NLMP] Section 3.3.2
func DeriveSigningKey ¶
func DeriveSigningKey(sessionBaseKey [16]byte, flags NegotiateFlag, encryptedKey []byte) [16]byte
DeriveSigningKey derives the final signing key from the session base key.
When the NTLMSSP_NEGOTIATE_KEY_EXCH (0x40000000) flag is negotiated:
- The client generates a random 16-byte ExportedSessionKey
- The client encrypts it with RC4 using SessionBaseKey
- The encrypted key is sent in EncryptedRandomSessionKey
- Server decrypts to obtain ExportedSessionKey
- ExportedSessionKey is used for message signing
When KEY_EXCH is NOT negotiated:
- SessionBaseKey is used directly for signing
This function handles both cases transparently.
Parameters:
- sessionBaseKey: The session key from ValidateNTLMv2Response
- flags: NegotiateFlags from the AUTHENTICATE message
- encryptedKey: EncryptedRandomSessionKey from AUTHENTICATE message (may be nil)
Returns the signing key to use for message signing.
func IsValid ¶
IsValid checks if the buffer starts with the NTLMSSP signature. Returns false if the buffer is too short (< 12 bytes) or has wrong signature. [MS-NLMP] Section 2.2.1
func ValidateNTLMv2Response ¶
func ValidateNTLMv2Response( ntHash [16]byte, username, domain string, serverChallenge [8]byte, ntResponse []byte, ) ([16]byte, error)
ValidateNTLMv2Response validates the client's NTLMv2 response and returns the session key.
The NTLMv2 response structure is:
- NTProofStr (16 bytes): HMAC-MD5(NTLMv2Hash, ServerChallenge + ClientBlob)
- ClientBlob (variable): Contains timestamp, nonce, and target info
Validation process:
- Extract NTProofStr (first 16 bytes) and ClientBlob (rest) from response
- Compute expected NTProofStr = HMAC-MD5(NTLMv2Hash, ServerChallenge + ClientBlob)
- Compare with provided NTProofStr
- If match, compute session key = HMAC-MD5(NTLMv2Hash, NTProofStr)
Returns the 16-byte session key on success, or error on failure.
[MS-NLMP] Section 3.3.2
Types ¶
type AuthenticateMessage ¶
type AuthenticateMessage struct {
// LmChallengeResponse contains the LM response to the server challenge.
// For NTLMv2, this is typically empty or contains LMv2 response.
LmChallengeResponse []byte
// NtChallengeResponse contains the NT response to the server challenge.
// For NTLMv2, this includes the NTProofStr and client blob.
NtChallengeResponse []byte
// Domain is the authentication domain.
// May be empty for local authentication.
Domain string
// Username is the account name.
// This is the key for looking up the user in DittoFS UserStore.
Username string
// Workstation is the client workstation name.
// Used for logging and auditing.
Workstation string
// NegotiateFlags contains the negotiated flags.
NegotiateFlags NegotiateFlag
// EncryptedRandomSessionKey contains the encrypted session key when KEY_EXCH is negotiated.
// If KEY_EXCH flag is set, this is decrypted with RC4 using SessionBaseKey
// to obtain the ExportedSessionKey, which is then used for signing.
EncryptedRandomSessionKey []byte
// IsAnonymous indicates if this is an anonymous authentication request.
// Set when FlagAnonymous is present in NegotiateFlags.
IsAnonymous bool
}
AuthenticateMessage contains parsed fields from an NTLM Type 3 message.
This structure holds the client's authentication response including:
- Username and domain for user lookup
- Challenge responses for credential validation (if implementing NTLMv2)
- Workstation name for logging/auditing
[MS-NLMP] Section 2.2.1.3
func ParseAuthenticate ¶
func ParseAuthenticate(buf []byte) (*AuthenticateMessage, error)
ParseAuthenticate parses an NTLM Type 3 (AUTHENTICATE) message.
This function extracts the authentication fields from a Type 3 message:
- Username and domain for user lookup
- Challenge responses for potential credential validation
- Workstation name for logging
Note: This implementation extracts the fields but does not validate the NTLMv2 responses. For full credential validation, the server would need to:
- Store the ServerChallenge from the Type 2 message
- Compute the expected NTProofStr using the user's NT hash
- Compare with the client's NtChallengeResponse
[MS-NLMP] Section 2.2.1.3
type AvID ¶
type AvID uint16
AvID represents AV_PAIR attribute IDs for the TargetInfo field. Each AV_PAIR has: AvId (2 bytes) + AvLen (2 bytes) + Value (AvLen bytes) [MS-NLMP] Section 2.2.2.1
const ( // AvEOL (0x0000) marks end of AV_PAIR list. // Every TargetInfo MUST end with this terminator. AvEOL AvID = 0x0000 // AvNbComputerName (0x0001) contains the server's NetBIOS name. // Example: "FILESERVER" AvNbComputerName AvID = 0x0001 // AvNbDomainName (0x0002) contains the NetBIOS domain name. // Example: "CORP" or "WORKGROUP" for standalone servers AvNbDomainName AvID = 0x0002 // AvDnsComputerName (0x0003) contains the server's DNS hostname. // Example: "fileserver.corp.com" AvDnsComputerName AvID = 0x0003 // AvDnsDomainName (0x0004) contains the DNS domain name. // Example: "corp.com" AvDnsDomainName AvID = 0x0004 // AvTimestamp (0x0007) contains the server's FILETIME timestamp. // Used by NTLMv2 for replay protection. AvTimestamp AvID = 0x0007 )
type Error ¶
type Error string
Error types for NTLM message parsing.
const ( // ErrMessageTooShort is returned when the buffer is too small for the message type. ErrMessageTooShort Error = "ntlm: message too short" // ErrInvalidSignature is returned when the NTLMSSP signature is missing or invalid. ErrInvalidSignature Error = "ntlm: invalid signature" // ErrWrongMessageType is returned when parsing a message of unexpected type. ErrWrongMessageType Error = "ntlm: wrong message type" // ErrAuthenticationFailed is returned when NTLMv2 validation fails. ErrAuthenticationFailed Error = "ntlm: authentication failed" // ErrResponseTooShort is returned when NT response is too short. ErrResponseTooShort Error = "ntlm: response too short" )
type MessageType ¶
type MessageType uint32
MessageType identifies the three messages in the NTLM handshake. [MS-NLMP] Section 2.2.1
const ( // Negotiate (Type 1) is sent by the client to initiate authentication. // Contains client capabilities and optional domain/workstation names. Negotiate MessageType = 1 // Challenge (Type 2) is sent by the server in response to Type 1. // Contains the server challenge and negotiated flags. Challenge MessageType = 2 // Authenticate (Type 3) is sent by the client to complete authentication. // Contains the challenge response computed from user credentials. Authenticate MessageType = 3 )
func GetMessageType ¶
func GetMessageType(buf []byte) MessageType
GetMessageType returns the NTLM message type from a buffer. Returns 0 if the buffer is too short or doesn't have a valid NTLM signature. Valid return values are: Negotiate (1), Challenge (2), Authenticate (3) [MS-NLMP] Section 2.2.1
type NegState ¶
type NegState int
NegState represents the state of SPNEGO negotiation. [RFC 4178] Section 4.2.2
const ( // NegStateAcceptCompleted indicates successful authentication. NegStateAcceptCompleted NegState = 0 // NegStateAcceptIncomplete indicates more tokens are needed. NegStateAcceptIncomplete NegState = 1 // NegStateReject indicates authentication was rejected. NegStateReject NegState = 2 // NegStateRequestMIC indicates a MIC is required. NegStateRequestMIC NegState = 3 )
type NegotiateFlag ¶
type NegotiateFlag uint32
NegotiateFlag controls authentication behavior and capabilities. These flags are exchanged in Type 1, Type 2, and Type 3 messages. [MS-NLMP] Section 2.2.2.5
const ( // FlagUnicode (bit A) indicates Unicode character set encoding. // When set, strings are encoded as UTF-16LE. FlagUnicode NegotiateFlag = 0x00000001 // FlagOEM (bit B) indicates OEM character set encoding. // When set, strings use the OEM code page. FlagOEM NegotiateFlag = 0x00000002 // FlagRequestTarget (bit C) requests the server's authentication realm. // Server responds with TargetName in Type 2 message. FlagRequestTarget NegotiateFlag = 0x00000004 // FlagSign (bit D) indicates message integrity support. // Enables MAC generation for signed messages. FlagSign NegotiateFlag = 0x00000010 // FlagSeal (bit E) indicates message confidentiality support. // Enables encryption for sealed messages. FlagSeal NegotiateFlag = 0x00000020 // FlagLMKey (bit G) indicates LAN Manager session key computation. // Deprecated; should not be used with NTLMv2. FlagLMKey NegotiateFlag = 0x00000080 // FlagNTLM (bit I) indicates NTLM v1 authentication support. // Required for compatibility with older clients. FlagNTLM NegotiateFlag = 0x00000200 // FlagAnonymous (bit K) indicates anonymous authentication. // Used when client has no credentials. FlagAnonymous NegotiateFlag = 0x00000800 // FlagDomainSupplied (bit L) indicates domain name is present. // Set when Type 1 message contains domain name. FlagDomainSupplied NegotiateFlag = 0x00001000 // FlagWorkstationSupplied (bit M) indicates workstation name is present. // Set when Type 1 message contains workstation name. FlagWorkstationSupplied NegotiateFlag = 0x00002000 // FlagAlwaysSign (bit O) requires signing for all messages. // Even if signing is not negotiated, dummy signature is included. FlagAlwaysSign NegotiateFlag = 0x00008000 // FlagTargetTypeDomain (bit P) indicates target is a domain. // Mutually exclusive with FlagTargetTypeServer. FlagTargetTypeDomain NegotiateFlag = 0x00010000 // FlagTargetTypeServer (bit Q) indicates target is a server. // Mutually exclusive with FlagTargetTypeDomain. FlagTargetTypeServer NegotiateFlag = 0x00020000 // FlagExtendedSecurity (bit S) indicates extended session security. // Enables NTLMv2 session security. FlagExtendedSecurity NegotiateFlag = 0x00080000 // FlagTargetInfo (bit W) indicates TargetInfo is present. // Type 2 message includes AV_PAIR list. FlagTargetInfo NegotiateFlag = 0x00800000 // FlagVersion (bit Y) indicates version field is present. // Includes OS version information. FlagVersion NegotiateFlag = 0x02000000 // Flag128 (bit Z) indicates 128-bit encryption support. // Required for strong encryption. Flag128 NegotiateFlag = 0x20000000 // FlagKeyExch (bit V) indicates key exchange support. // When set, the client generates a random session key (ExportedSessionKey) // and encrypts it with RC4 using the SessionBaseKey. The encrypted key // is sent in the EncryptedRandomSessionKey field of the AUTHENTICATE message. // The ExportedSessionKey becomes the SigningKey instead of SessionBaseKey. FlagKeyExch NegotiateFlag = 0x40000000 // Flag56 (bit AA) indicates 56-bit encryption support. // Legacy; 128-bit is preferred. Flag56 NegotiateFlag = 0x80000000 )
type ParsedToken ¶
type ParsedToken struct {
// Type indicates whether this is an init or response token.
Type TokenType
// MechTypes lists the mechanisms offered (only for TokenTypeInit).
MechTypes []asn1.ObjectIdentifier
// MechToken is the inner mechanism token (e.g., NTLM message).
MechToken []byte
// NegState is the negotiation state (only for TokenTypeResp).
NegState NegState
// SupportedMech is the selected mechanism (only for TokenTypeResp).
SupportedMech asn1.ObjectIdentifier
}
ParsedToken contains the result of parsing a SPNEGO token.
func Parse ¶
func Parse(data []byte) (*ParsedToken, error)
Parse parses a SPNEGO token and extracts its contents.
The input can be either:
- A GSSAPI-wrapped token (starts with 0x60)
- A raw NegTokenInit (starts with 0xa0)
- A raw NegTokenResp (starts with 0xa1)
Returns a ParsedToken containing the mechanism token and metadata.
func (*ParsedToken) HasKerberos ¶
func (p *ParsedToken) HasKerberos() bool
HasKerberos returns true if the token offers Kerberos authentication.
func (*ParsedToken) HasMechanism ¶
func (p *ParsedToken) HasMechanism(oid asn1.ObjectIdentifier) bool
HasMechanism checks if the parsed token offers a specific mechanism. Only valid for TokenTypeInit tokens.
func (*ParsedToken) HasNTLM ¶
func (p *ParsedToken) HasNTLM() bool
HasNTLM returns true if the token offers NTLM authentication.
type SMBAuthenticator ¶
type SMBAuthenticator struct {
// contains filtered or unexported fields
}
SMBAuthenticator implements adapter.Authenticator for SMB protocol authentication.
It handles SPNEGO-wrapped tokens, detecting whether the client is using NTLM or Kerberos, and delegates to the appropriate authentication mechanism.
For NTLM, the authenticator manages multi-round state internally using session IDs to correlate Type 1 (NEGOTIATE) and Type 3 (AUTHENTICATE) messages.
For Kerberos, authentication completes in a single round (placeholder for Phase 39+).
Thread safety: Safe for concurrent use. Each authentication session is tracked independently using atomic session IDs and sync.Map for pending state.
func NewSMBAuthenticator ¶
func NewSMBAuthenticator(userStore models.UserStore) *SMBAuthenticator
NewSMBAuthenticator creates a new SMBAuthenticator.
Parameters:
- userStore: User store for looking up DittoFS users by username. May be nil, in which case all authentications resolve to guest.
func (*SMBAuthenticator) Authenticate ¶
func (a *SMBAuthenticator) Authenticate(ctx context.Context, token []byte) (*adapter.AuthResult, []byte, error)
Authenticate processes an SMB authentication token (typically SPNEGO-wrapped).
The token is first parsed as SPNEGO to detect the underlying mechanism:
- NTLM: Two-round authentication via challenge-response
- Kerberos: Single-round authentication (placeholder, returns error)
- Raw NTLM: Handled directly if SPNEGO parsing fails
For NTLM Type 1 (NEGOTIATE):
Returns (nil, challengeToken, ErrMoreProcessingRequired) The challengeToken is an SPNEGO-wrapped NTLM Type 2 (CHALLENGE) message.
For NTLM Type 3 (AUTHENTICATE):
Returns (result, nil, nil) on success with the authenticated user. Returns (nil, nil, error) on authentication failure.