github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/upstreamproxy/go-ntlm/ntlm/message_challenge.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 ChallengeMessage struct {
    14  	// sig - 8 bytes
    15  	Signature []byte
    16  	// message type - 4 bytes
    17  	MessageType uint32
    18  	// targetname - 12 bytes
    19  	TargetName *PayloadStruct
    20  	// negotiate flags - 4bytes
    21  	NegotiateFlags uint32
    22  	// server challenge - 8 bytes
    23  	ServerChallenge []byte
    24  
    25  	// MS-NLMP and Davenport disagree a little on the next few fields and how optional they are
    26  	// This is what Davenport has to say:
    27  	// As with the Type 1 message, there are a few versions of the Type 2 that have been observed:
    28  	//
    29  	// Version 1 -- The Context, Target Information, and OS Version structure are all omitted. The data block
    30  	// (containing only the contents of the Target Name security buffer) begins at offset 32. This form
    31  	// is seen in older Win9x-based systems, and is roughly documented in the Open Group's ActiveX reference
    32  	// documentation (Section 11.2.3).
    33  	//
    34  	// Version 2 -- The Context and Target Information fields are present, but the OS Version structure is not.
    35  	// The data block begins after the Target Information header, at offset 48. This form is seen in most out-of-box
    36  	// shipping versions of Windows.
    37  	//
    38  	// Version 3 -- The Context, Target Information, and OS Version structure are all present. The data block begins
    39  	// after the OS Version structure, at offset 56. Again, the buffers may be empty (yielding a zero-length data block).
    40  	// This form was introduced in a relatively recent Service Pack, and is seen on currently-patched versions of Windows 2000,
    41  	// Windows XP, and Windows 2003.
    42  
    43  	// reserved - 8 bytes (set to 0). This field is also known as 'context' in the davenport documentation
    44  	Reserved []byte
    45  
    46  	// targetinfo  - 12 bytes
    47  	TargetInfoPayloadStruct *PayloadStruct
    48  	TargetInfo              *AvPairs
    49  
    50  	// version - 8 bytes
    51  	Version *VersionStruct
    52  	// payload - variable
    53  	Payload []byte
    54  }
    55  
    56  func ParseChallengeMessage(body []byte) (*ChallengeMessage, error) {
    57  	challenge := new(ChallengeMessage)
    58  
    59  	// [Psiphon]
    60  	// Don't panic on malformed remote input.
    61  	if len(body) < 40 {
    62  		return nil, errors.New("invalid challenge message")
    63  	}
    64  
    65  	challenge.Signature = body[0:8]
    66  	if !bytes.Equal(challenge.Signature, []byte("NTLMSSP\x00")) {
    67  		return challenge, errors.New("Invalid NTLM message signature")
    68  	}
    69  
    70  	challenge.MessageType = binary.LittleEndian.Uint32(body[8:12])
    71  	if challenge.MessageType != 2 {
    72  		return challenge, errors.New("Invalid NTLM message type should be 0x00000002 for challenge message")
    73  	}
    74  
    75  	var err error
    76  
    77  	challenge.TargetName, err = ReadStringPayload(12, body)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	challenge.NegotiateFlags = binary.LittleEndian.Uint32(body[20:24])
    83  
    84  	challenge.ServerChallenge = body[24:32]
    85  
    86  	challenge.Reserved = body[32:40]
    87  
    88  	challenge.TargetInfoPayloadStruct, err = ReadBytePayload(40, body)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	challenge.TargetInfo, err = ReadAvPairs(challenge.TargetInfoPayloadStruct.Payload)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	offset := 48
    99  
   100  	if NTLMSSP_NEGOTIATE_VERSION.IsSet(challenge.NegotiateFlags) {
   101  
   102  		// [Psiphon]
   103  		// Don't panic on malformed remote input.
   104  		if len(body) < offset+8 {
   105  			return nil, errors.New("invalid challenge message")
   106  		}
   107  
   108  		challenge.Version, err = ReadVersionStruct(body[offset : offset+8])
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  		offset = offset + 8
   113  	}
   114  
   115  	// [Psiphon]
   116  	// Don't panic on malformed remote input.
   117  	if len(body) < offset {
   118  		return nil, errors.New("invalid challenge message")
   119  	}
   120  
   121  	challenge.Payload = body[offset:]
   122  
   123  	return challenge, nil
   124  }
   125  
   126  func (c *ChallengeMessage) Bytes() []byte {
   127  	payloadLen := int(c.TargetName.Len + c.TargetInfoPayloadStruct.Len)
   128  	messageLen := 8 + 4 + 8 + 4 + 8 + 8 + 8 + 8
   129  	payloadOffset := uint32(messageLen)
   130  
   131  	messageBytes := make([]byte, 0, messageLen+payloadLen)
   132  	buffer := bytes.NewBuffer(messageBytes)
   133  
   134  	buffer.Write(c.Signature)
   135  	binary.Write(buffer, binary.LittleEndian, c.MessageType)
   136  
   137  	c.TargetName.Offset = payloadOffset
   138  	buffer.Write(c.TargetName.Bytes())
   139  	payloadOffset += uint32(c.TargetName.Len)
   140  
   141  	binary.Write(buffer, binary.LittleEndian, c.NegotiateFlags)
   142  	buffer.Write(c.ServerChallenge)
   143  	buffer.Write(make([]byte, 8))
   144  
   145  	c.TargetInfoPayloadStruct.Offset = payloadOffset
   146  	buffer.Write(c.TargetInfoPayloadStruct.Bytes())
   147  	payloadOffset += uint32(c.TargetInfoPayloadStruct.Len)
   148  
   149  	// if(c.Version != nil) {
   150  	buffer.Write(c.Version.Bytes())
   151  	// } else {
   152  	//  buffer.Write(make([]byte, 8))
   153  	//}
   154  
   155  	// Write out the payloads
   156  	buffer.Write(c.TargetName.Payload)
   157  	buffer.Write(c.TargetInfoPayloadStruct.Payload)
   158  
   159  	return buffer.Bytes()
   160  }
   161  
   162  func (c *ChallengeMessage) getLowestPayloadOffset() int {
   163  	payloadStructs := [...]*PayloadStruct{c.TargetName, c.TargetInfoPayloadStruct}
   164  
   165  	// Find the lowest offset value
   166  	lowest := 9999
   167  	for i := range payloadStructs {
   168  		p := payloadStructs[i]
   169  		if p != nil && p.Offset > 0 && int(p.Offset) < lowest {
   170  			lowest = int(p.Offset)
   171  		}
   172  	}
   173  
   174  	return lowest
   175  }
   176  
   177  func (c *ChallengeMessage) String() string {
   178  	var buffer bytes.Buffer
   179  
   180  	buffer.WriteString("Challenge NTLM Message")
   181  	buffer.WriteString(fmt.Sprintf("\nPayload Offset: %d Length: %d", c.getLowestPayloadOffset(), len(c.Payload)))
   182  	buffer.WriteString(fmt.Sprintf("\nTargetName: %s", c.TargetName.String()))
   183  	buffer.WriteString(fmt.Sprintf("\nServerChallenge: %s", hex.EncodeToString(c.ServerChallenge)))
   184  	if c.Version != nil {
   185  		buffer.WriteString(fmt.Sprintf("\nVersion: %s\n", c.Version.String()))
   186  	}
   187  	buffer.WriteString("\nTargetInfo")
   188  	buffer.WriteString(c.TargetInfo.String())
   189  	buffer.WriteString(fmt.Sprintf("\nFlags %d\n", c.NegotiateFlags))
   190  	buffer.WriteString(FlagsToString(c.NegotiateFlags))
   191  
   192  	return buffer.String()
   193  }