github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/upstreamproxy/go-ntlm/ntlm/message_authenticate.go (about)

     1  //Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information
     2  
     3  package ntlm
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/binary"
     8  	"encoding/hex"
     9  	"errors"
    10  	"fmt"
    11  )
    12  
    13  type AuthenticateMessage struct {
    14  	// sig - 8 bytes
    15  	Signature []byte
    16  	// message type - 4 bytes
    17  	MessageType uint32
    18  
    19  	// The LmChallenge Response can be v1 or v2
    20  	LmChallengeResponse *PayloadStruct // 8 bytes
    21  	LmV1Response        *LmV1Response
    22  	LmV2Response        *LmV2Response
    23  
    24  	// The NtChallengeResponse can be v1 or v2
    25  	NtChallengeResponseFields *PayloadStruct // 8 bytes
    26  	NtlmV1Response            *NtlmV1Response
    27  	NtlmV2Response            *NtlmV2Response
    28  
    29  	DomainName  *PayloadStruct // 8 bytes
    30  	UserName    *PayloadStruct // 8 bytes
    31  	Workstation *PayloadStruct // 8 bytes
    32  
    33  	// If the NTLMSSP_NEGOTIATE_KEY_EXCH flag is set in the neogitate flags then this will point to the offset in the payload
    34  	// with the key, otherwise it will have Len = 0. According to Davenport these bytes are optional (see Type3 message).
    35  	// The MS-NLMP docs do not mention this.
    36  	EncryptedRandomSessionKey *PayloadStruct // 8 bytes
    37  
    38  	/// MS-NLMP 2.2.1.3 - In connectionless mode, a NEGOTIATE structure that contains a set of bit flags (section 2.2.2.5) and represents the
    39  	// conclusion of negotiation—the choices the client has made from the options the server offered in the CHALLENGE_MESSAGE.
    40  	// In connection-oriented mode, a NEGOTIATE structure that contains the set of bit flags (section 2.2.2.5) negotiated in
    41  	// the previous
    42  	NegotiateFlags uint32 // 4 bytes
    43  
    44  	// Version (8 bytes): A VERSION structure (section 2.2.2.10) that is present only when the NTLMSSP_NEGOTIATE_VERSION
    45  	// flag is set in the NegotiateFlags field. This structure is used for debugging purposes only. In normal protocol
    46  	// messages, it is ignored and does not affect the NTLM message processing.<9>
    47  	Version *VersionStruct
    48  
    49  	// The message integrity for the NTLM NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE, and AUTHENTICATE_MESSAGE.<10>
    50  	Mic []byte // 16 bytes
    51  
    52  	// payload - variable
    53  	Payload []byte
    54  }
    55  
    56  func ParseAuthenticateMessage(body []byte, ntlmVersion int) (*AuthenticateMessage, error) {
    57  	am := new(AuthenticateMessage)
    58  
    59  	// [Psiphon]
    60  	// Don't panic on malformed remote input.
    61  	if len(body) < 12 {
    62  		return nil, errors.New("invalid authenticate message")
    63  	}
    64  
    65  	am.Signature = body[0:8]
    66  	if !bytes.Equal(am.Signature, []byte("NTLMSSP\x00")) {
    67  		return nil, errors.New("Invalid NTLM message signature")
    68  	}
    69  
    70  	am.MessageType = binary.LittleEndian.Uint32(body[8:12])
    71  	if am.MessageType != 3 {
    72  		return nil, errors.New("Invalid NTLM message type should be 0x00000003 for authenticate message")
    73  	}
    74  
    75  	var err error
    76  
    77  	am.LmChallengeResponse, err = ReadBytePayload(12, body)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	if ntlmVersion == 2 {
    83  		am.LmV2Response, err = ReadLmV2Response(am.LmChallengeResponse.Payload)
    84  	} else {
    85  		am.LmV1Response, err = ReadLmV1Response(am.LmChallengeResponse.Payload)
    86  	}
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	am.NtChallengeResponseFields, err = ReadBytePayload(20, body)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	// Check to see if this is a v1 or v2 response
    97  	if ntlmVersion == 2 {
    98  		am.NtlmV2Response, err = ReadNtlmV2Response(am.NtChallengeResponseFields.Payload)
    99  	} else {
   100  		am.NtlmV1Response, err = ReadNtlmV1Response(am.NtChallengeResponseFields.Payload)
   101  	}
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	am.DomainName, err = ReadStringPayload(28, body)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	am.UserName, err = ReadStringPayload(36, body)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	am.Workstation, err = ReadStringPayload(44, body)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	lowestOffset := am.getLowestPayloadOffset()
   122  	offset := 52
   123  
   124  	// If the lowest payload offset is 52 then:
   125  	// The Session Key, flags, and OS Version structure are omitted. The data (payload) block in this case starts after the Workstation Name
   126  	// security buffer header, at offset 52. This form is seen in older Win9x-based systems. This is from the davenport notes about Type 3
   127  	// messages and this information does not seem to be present in the MS-NLMP document
   128  	if lowestOffset > 52 {
   129  		am.EncryptedRandomSessionKey, err = ReadBytePayload(offset, body)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  		offset = offset + 8
   134  
   135  		// [Psiphon]
   136  		// Don't panic on malformed remote input.
   137  		if len(body) < offset+4 {
   138  			return nil, errors.New("invalid authenticate message")
   139  		}
   140  
   141  		am.NegotiateFlags = binary.LittleEndian.Uint32(body[offset : offset+4])
   142  		offset = offset + 4
   143  
   144  		// Version (8 bytes): A VERSION structure (section 2.2.2.10) that is present only when the NTLMSSP_NEGOTIATE_VERSION flag is set in the NegotiateFlags field. This structure is used for debugging purposes only. In normal protocol messages, it is ignored and does not affect the NTLM message processing.<9>
   145  		if NTLMSSP_NEGOTIATE_VERSION.IsSet(am.NegotiateFlags) {
   146  
   147  			// [Psiphon]
   148  			// Don't panic on malformed remote input.
   149  			if len(body) < offset+8 {
   150  				return nil, errors.New("invalid authenticate message")
   151  			}
   152  
   153  			am.Version, err = ReadVersionStruct(body[offset : offset+8])
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  			offset = offset + 8
   158  		}
   159  
   160  		// The MS-NLMP has this to say about the MIC
   161  		//   "An AUTHENTICATE_MESSAGE indicates the presence of a MIC field if the TargetInfo field has an AV_PAIR structure whose two fields are:
   162  		//   AvId == MsvAvFlags Value bit 0x2 == 1"
   163  		// However there is no TargetInfo structure in the Authenticate Message! There is one in the Challenge Message though. So I'm using
   164  		// a hack to check to see if there is a MIC. I look to see if there is room for the MIC before the payload starts. If so I assume
   165  		// there is a MIC and read it out.
   166  		var lowestOffset = am.getLowestPayloadOffset()
   167  		if lowestOffset > offset {
   168  
   169  			// [Psiphon]
   170  			// Don't panic on malformed remote input.
   171  			if len(body) < offset+16 {
   172  				return nil, errors.New("invalid authenticate message")
   173  			}
   174  
   175  			// MIC - 16 bytes
   176  			am.Mic = body[offset : offset+16]
   177  			offset = offset + 16
   178  		}
   179  	}
   180  
   181  	// [Psiphon]
   182  	// Don't panic on malformed remote input.
   183  	if len(body) < offset {
   184  		return nil, errors.New("invalid authenticate message")
   185  	}
   186  
   187  	am.Payload = body[offset:]
   188  
   189  	return am, nil
   190  }
   191  
   192  func (a *AuthenticateMessage) ClientChallenge() (response []byte) {
   193  	if a.NtlmV2Response != nil {
   194  		response = a.NtlmV2Response.NtlmV2ClientChallenge.ChallengeFromClient
   195  	} else if a.NtlmV1Response != nil && NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(a.NegotiateFlags) {
   196  		response = a.LmV1Response.Response[0:8]
   197  	}
   198  
   199  	return response
   200  }
   201  
   202  func (a *AuthenticateMessage) getLowestPayloadOffset() int {
   203  	payloadStructs := [...]*PayloadStruct{a.LmChallengeResponse, a.NtChallengeResponseFields, a.DomainName, a.UserName, a.Workstation, a.EncryptedRandomSessionKey}
   204  
   205  	// Find the lowest offset value
   206  	lowest := 9999
   207  	for i := range payloadStructs {
   208  		p := payloadStructs[i]
   209  		if p != nil && p.Offset > 0 && int(p.Offset) < lowest {
   210  			lowest = int(p.Offset)
   211  		}
   212  	}
   213  
   214  	return lowest
   215  }
   216  
   217  func (a *AuthenticateMessage) Bytes() []byte {
   218  	payloadLen := int(a.LmChallengeResponse.Len + a.NtChallengeResponseFields.Len + a.DomainName.Len + a.UserName.Len + a.Workstation.Len + a.EncryptedRandomSessionKey.Len)
   219  	messageLen := 8 + 4 + 6*8 + 4 + 8 + 16
   220  	payloadOffset := uint32(messageLen)
   221  
   222  	messageBytes := make([]byte, 0, messageLen+payloadLen)
   223  	buffer := bytes.NewBuffer(messageBytes)
   224  
   225  	buffer.Write(a.Signature)
   226  
   227  	binary.Write(buffer, binary.LittleEndian, a.MessageType)
   228  
   229  	a.LmChallengeResponse.Offset = payloadOffset
   230  	payloadOffset += uint32(a.LmChallengeResponse.Len)
   231  	buffer.Write(a.LmChallengeResponse.Bytes())
   232  
   233  	a.NtChallengeResponseFields.Offset = payloadOffset
   234  	payloadOffset += uint32(a.NtChallengeResponseFields.Len)
   235  	buffer.Write(a.NtChallengeResponseFields.Bytes())
   236  
   237  	a.DomainName.Offset = payloadOffset
   238  	payloadOffset += uint32(a.DomainName.Len)
   239  	buffer.Write(a.DomainName.Bytes())
   240  
   241  	a.UserName.Offset = payloadOffset
   242  	payloadOffset += uint32(a.UserName.Len)
   243  	buffer.Write(a.UserName.Bytes())
   244  
   245  	a.Workstation.Offset = payloadOffset
   246  	payloadOffset += uint32(a.Workstation.Len)
   247  	buffer.Write(a.Workstation.Bytes())
   248  
   249  	a.EncryptedRandomSessionKey.Offset = payloadOffset
   250  	payloadOffset += uint32(a.EncryptedRandomSessionKey.Len)
   251  	buffer.Write(a.EncryptedRandomSessionKey.Bytes())
   252  
   253  	buffer.Write(uint32ToBytes(a.NegotiateFlags))
   254  
   255  	if a.Version != nil {
   256  		buffer.Write(a.Version.Bytes())
   257  	} else {
   258  		buffer.Write(make([]byte, 8))
   259  	}
   260  
   261  	if a.Mic != nil {
   262  		buffer.Write(a.Mic)
   263  	} else {
   264  		buffer.Write(make([]byte, 16))
   265  	}
   266  
   267  	// Write out the payloads
   268  	buffer.Write(a.LmChallengeResponse.Payload)
   269  	buffer.Write(a.NtChallengeResponseFields.Payload)
   270  	buffer.Write(a.DomainName.Payload)
   271  	buffer.Write(a.UserName.Payload)
   272  	buffer.Write(a.Workstation.Payload)
   273  	buffer.Write(a.EncryptedRandomSessionKey.Payload)
   274  
   275  	return buffer.Bytes()
   276  }
   277  
   278  func (a *AuthenticateMessage) String() string {
   279  	var buffer bytes.Buffer
   280  
   281  	buffer.WriteString("Authenticate NTLM Message\n")
   282  	buffer.WriteString(fmt.Sprintf("Payload Offset: %d Length: %d\n", a.getLowestPayloadOffset(), len(a.Payload)))
   283  
   284  	if a.LmV2Response != nil {
   285  		buffer.WriteString(a.LmV2Response.String())
   286  		buffer.WriteString("\n")
   287  	}
   288  
   289  	if a.LmV1Response != nil {
   290  		buffer.WriteString(a.LmV1Response.String())
   291  		buffer.WriteString("\n")
   292  	}
   293  
   294  	if a.NtlmV2Response != nil {
   295  		buffer.WriteString(a.NtlmV2Response.String())
   296  		buffer.WriteString("\n")
   297  	}
   298  
   299  	if a.NtlmV1Response != nil {
   300  		buffer.WriteString(fmt.Sprintf("NtlmResponse Length: %d\n", a.NtChallengeResponseFields.Len))
   301  		buffer.WriteString(a.NtlmV1Response.String())
   302  		buffer.WriteString("\n")
   303  	}
   304  
   305  	buffer.WriteString(fmt.Sprintf("UserName: %s\n", a.UserName.String()))
   306  	buffer.WriteString(fmt.Sprintf("DomainName: %s\n", a.DomainName.String()))
   307  	buffer.WriteString(fmt.Sprintf("Workstation: %s\n", a.Workstation.String()))
   308  
   309  	if a.EncryptedRandomSessionKey != nil {
   310  		buffer.WriteString(fmt.Sprintf("EncryptedRandomSessionKey: %s\n", a.EncryptedRandomSessionKey.String()))
   311  	}
   312  
   313  	if a.Version != nil {
   314  		buffer.WriteString(fmt.Sprintf("Version: %s\n", a.Version.String()))
   315  	}
   316  
   317  	if a.Mic != nil {
   318  		buffer.WriteString(fmt.Sprintf("MIC: %s\n", hex.EncodeToString(a.Mic)))
   319  	}
   320  
   321  	buffer.WriteString(fmt.Sprintf("Flags %d\n", a.NegotiateFlags))
   322  	buffer.WriteString(FlagsToString(a.NegotiateFlags))
   323  
   324  	return buffer.String()
   325  }