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 }