github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/upstreamproxy/go-ntlm/ntlm/ntlmv2.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 rc4P "crypto/rc4" 8 "encoding/binary" 9 "errors" 10 "strings" 11 "time" 12 ) 13 14 /******************************* 15 Shared Session Data and Methods 16 *******************************/ 17 18 type V2Session struct { 19 SessionData 20 } 21 22 func (n *V2Session) SetUserInfo(username string, password string, domain string) { 23 n.user = username 24 n.password = password 25 n.userDomain = domain 26 } 27 28 func (n *V2Session) GetUserInfo() (string, string, string) { 29 return n.user, n.password, n.userDomain 30 } 31 32 func (n *V2Session) SetMode(mode Mode) { 33 n.mode = mode 34 } 35 36 func (n *V2Session) Version() int { 37 return 2 38 } 39 40 func (n *V2Session) fetchResponseKeys() (err error) { 41 // Usually at this point we'd go out to Active Directory and get these keys 42 // Here we are assuming we have the information locally 43 n.responseKeyLM = lmowfv2(n.user, n.password, n.userDomain) 44 n.responseKeyNT = ntowfv2(n.user, n.password, n.userDomain) 45 return 46 } 47 48 func (n *V2ServerSession) GetSessionData() *SessionData { 49 return &n.SessionData 50 } 51 52 // Define ComputeResponse(NegFlg, ResponseKeyNT, ResponseKeyLM, CHALLENGE_MESSAGE.ServerChallenge, ClientChallenge, Time, ServerName) 53 // ServerNameBytes - The NtChallengeResponseFields.NTLMv2_RESPONSE.NTLMv2_CLIENT_CHALLENGE.AvPairs field structure of the AUTHENTICATE_MESSAGE payload. 54 func (n *V2Session) computeExpectedResponses(timestamp []byte, avPairBytes []byte) (err error) { 55 temp := concat([]byte{0x01}, []byte{0x01}, zeroBytes(6), timestamp, n.clientChallenge, zeroBytes(4), avPairBytes, zeroBytes(4)) 56 ntProofStr := hmacMd5(n.responseKeyNT, concat(n.serverChallenge, temp)) 57 n.ntChallengeResponse = concat(ntProofStr, temp) 58 n.lmChallengeResponse = concat(hmacMd5(n.responseKeyLM, concat(n.serverChallenge, n.clientChallenge)), n.clientChallenge) 59 n.sessionBaseKey = hmacMd5(n.responseKeyNT, ntProofStr) 60 return 61 } 62 63 func (n *V2Session) computeKeyExchangeKey() (err error) { 64 n.keyExchangeKey = n.sessionBaseKey 65 return 66 } 67 68 func (n *V2Session) calculateKeys(ntlmRevisionCurrent uint8) (err error) { 69 // This lovely piece of code comes courtesy of an the excellent Open Document support system from MSFT 70 // In order to calculate the keys correctly when the client has set the NTLMRevisionCurrent to 0xF (15) 71 // We must treat the flags as if NTLMSSP_NEGOTIATE_LM_KEY is set. 72 // This information is not contained (at least currently, until they correct it) in the MS-NLMP document 73 if ntlmRevisionCurrent == 15 { 74 n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Set(n.NegotiateFlags) 75 } 76 77 n.ClientSigningKey = signKey(n.NegotiateFlags, n.exportedSessionKey, "Client") 78 n.ServerSigningKey = signKey(n.NegotiateFlags, n.exportedSessionKey, "Server") 79 n.ClientSealingKey = sealKey(n.NegotiateFlags, n.exportedSessionKey, "Client") 80 n.ServerSealingKey = sealKey(n.NegotiateFlags, n.exportedSessionKey, "Server") 81 return 82 } 83 84 func (n *V2Session) Seal(message []byte) ([]byte, error) { 85 return nil, nil 86 } 87 func (n *V2Session) Sign(message []byte) ([]byte, error) { 88 return nil, nil 89 } 90 91 func NtlmVCommonMac(message []byte, sequenceNumber int, sealingKey, signingKey []byte, NegotiateFlags uint32) []byte { 92 var handle *rc4P.Cipher 93 // TODO: Need to keep track of the sequence number for connection oriented NTLM 94 if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) && NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(NegotiateFlags) { 95 handle, _ = reinitSealingKey(sealingKey, sequenceNumber) 96 } else if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) { 97 // CONOR: Reinitializing the rc4 cipher on every requst, but not using the 98 // algorithm as described in the MS-NTLM document. Just reinitialize it directly. 99 handle, _ = rc4Init(sealingKey) 100 } 101 sig := mac(NegotiateFlags, handle, signingKey, uint32(sequenceNumber), message) 102 return sig.Bytes() 103 } 104 105 func NtlmV2Mac(message []byte, sequenceNumber int, handle *rc4P.Cipher, sealingKey, signingKey []byte, NegotiateFlags uint32) []byte { 106 // TODO: Need to keep track of the sequence number for connection oriented NTLM 107 if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) && NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(NegotiateFlags) { 108 handle, _ = reinitSealingKey(sealingKey, sequenceNumber) 109 } else if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) { 110 // CONOR: Reinitializing the rc4 cipher on every requst, but not using the 111 // algorithm as described in the MS-NTLM document. Just reinitialize it directly. 112 handle, _ = rc4Init(sealingKey) 113 } 114 sig := mac(NegotiateFlags, handle, signingKey, uint32(sequenceNumber), message) 115 return sig.Bytes() 116 } 117 118 func (n *V2ServerSession) Mac(message []byte, sequenceNumber int) ([]byte, error) { 119 mac := NtlmV2Mac(message, sequenceNumber, n.serverHandle, n.ServerSealingKey, n.ServerSigningKey, n.NegotiateFlags) 120 return mac, nil 121 } 122 123 func (n *V2ServerSession) VerifyMac(message, expectedMac []byte, sequenceNumber int) (bool, error) { 124 mac := NtlmV2Mac(message, sequenceNumber, n.clientHandle, n.ClientSealingKey, n.ClientSigningKey, n.NegotiateFlags) 125 return MacsEqual(mac, expectedMac), nil 126 } 127 128 func (n *V2ClientSession) Mac(message []byte, sequenceNumber int) ([]byte, error) { 129 mac := NtlmV2Mac(message, sequenceNumber, n.clientHandle, n.ClientSealingKey, n.ClientSigningKey, n.NegotiateFlags) 130 return mac, nil 131 } 132 133 func (n *V2ClientSession) VerifyMac(message, expectedMac []byte, sequenceNumber int) (bool, error) { 134 mac := NtlmV2Mac(message, sequenceNumber, n.serverHandle, n.ServerSealingKey, n.ServerSigningKey, n.NegotiateFlags) 135 return MacsEqual(mac, expectedMac), nil 136 } 137 138 /************** 139 Server Session 140 **************/ 141 142 type V2ServerSession struct { 143 V2Session 144 } 145 146 func (n *V2ServerSession) SetServerChallenge(challenge []byte) { 147 n.serverChallenge = challenge 148 } 149 150 func (n *V2ServerSession) ProcessNegotiateMessage(nm *NegotiateMessage) (err error) { 151 n.negotiateMessage = nm 152 return 153 } 154 155 func (n *V2ServerSession) GenerateChallengeMessage() (cm *ChallengeMessage, err error) { 156 cm = new(ChallengeMessage) 157 cm.Signature = []byte("NTLMSSP\x00") 158 cm.MessageType = uint32(2) 159 cm.TargetName, _ = CreateBytePayload(make([]byte, 0)) 160 161 flags := uint32(0) 162 flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) 163 flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags) 164 flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) 165 flags = NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags) 166 flags = NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags) 167 flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) 168 flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags) 169 flags = NTLMSSP_NEGOTIATE_DATAGRAM.Set(flags) 170 flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags) 171 flags = NTLMSSP_REQUEST_TARGET.Set(flags) 172 flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags) 173 flags = NTLMSSP_NEGOTIATE_128.Set(flags) 174 175 cm.NegotiateFlags = flags 176 177 n.serverChallenge = randomBytes(8) 178 cm.ServerChallenge = n.serverChallenge 179 cm.Reserved = make([]byte, 8) 180 181 // Create the AvPairs we need 182 pairs := new(AvPairs) 183 pairs.AddAvPair(MsvAvNbDomainName, utf16FromString("REUTERS")) 184 pairs.AddAvPair(MsvAvNbComputerName, utf16FromString("UKBP-CBTRMFE06")) 185 pairs.AddAvPair(MsvAvDnsDomainName, utf16FromString("Reuters.net")) 186 pairs.AddAvPair(MsvAvDnsComputerName, utf16FromString("ukbp-cbtrmfe06.Reuters.net")) 187 pairs.AddAvPair(MsvAvDnsTreeName, utf16FromString("Reuters.net")) 188 pairs.AddAvPair(MsvAvEOL, make([]byte, 0)) 189 cm.TargetInfo = pairs 190 cm.TargetInfoPayloadStruct, _ = CreateBytePayload(pairs.Bytes()) 191 192 cm.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} 193 return cm, nil 194 } 195 196 func (n *V2ServerSession) ProcessAuthenticateMessage(am *AuthenticateMessage) (err error) { 197 n.authenticateMessage = am 198 n.NegotiateFlags = am.NegotiateFlags 199 n.clientChallenge = am.ClientChallenge() 200 n.encryptedRandomSessionKey = am.EncryptedRandomSessionKey.Payload 201 // Ignore the values used in SetUserInfo and use these instead from the authenticate message 202 // They should always be correct (I hope) 203 n.user = am.UserName.String() 204 n.userDomain = am.DomainName.String() 205 206 err = n.fetchResponseKeys() 207 if err != nil { 208 return err 209 } 210 211 timestamp := am.NtlmV2Response.NtlmV2ClientChallenge.TimeStamp 212 avPairsBytes := am.NtlmV2Response.NtlmV2ClientChallenge.AvPairs.Bytes() 213 214 err = n.computeExpectedResponses(timestamp, avPairsBytes) 215 if err != nil { 216 return err 217 } 218 219 if !bytes.Equal(am.NtChallengeResponseFields.Payload, n.ntChallengeResponse) { 220 if !bytes.Equal(am.LmChallengeResponse.Payload, n.lmChallengeResponse) { 221 return errors.New("Could not authenticate") 222 } 223 } 224 225 err = n.computeKeyExchangeKey() 226 if err != nil { 227 return err 228 } 229 230 n.mic = am.Mic 231 am.Mic = zeroBytes(16) 232 233 err = n.computeExportedSessionKey() 234 if err != nil { 235 return err 236 } 237 238 if am.Version == nil { 239 //UGH not entirely sure how this could possibly happen, going to put this in for now 240 //TODO investigate if this ever is really happening 241 am.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} 242 243 } 244 245 err = n.calculateKeys(am.Version.NTLMRevisionCurrent) 246 if err != nil { 247 return err 248 } 249 250 n.clientHandle, err = rc4Init(n.ClientSealingKey) 251 if err != nil { 252 return err 253 } 254 n.serverHandle, err = rc4Init(n.ServerSealingKey) 255 if err != nil { 256 return err 257 } 258 259 return nil 260 } 261 262 func (n *V2ServerSession) computeExportedSessionKey() (err error) { 263 if NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.NegotiateFlags) { 264 n.exportedSessionKey, err = rc4K(n.keyExchangeKey, n.encryptedRandomSessionKey) 265 if err != nil { 266 return err 267 } 268 // TODO: Calculate mic correctly. This calculation is not producing the right results now 269 // n.calculatedMic = HmacMd5(n.exportedSessionKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes)) 270 } else { 271 n.exportedSessionKey = n.keyExchangeKey 272 // TODO: Calculate mic correctly. This calculation is not producing the right results now 273 // n.calculatedMic = HmacMd5(n.keyExchangeKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes)) 274 } 275 return nil 276 } 277 278 /************* 279 Client Session 280 **************/ 281 282 type V2ClientSession struct { 283 V2Session 284 } 285 286 func (n *V2ClientSession) GenerateNegotiateMessage() (nm *NegotiateMessage, err error) { 287 nm = new(NegotiateMessage) 288 nm.Signature = []byte("NTLMSSP\x00") 289 nm.MessageType = uint32(1) 290 nm.NegotiateFlags = NEGOTIATE_FLAG_REQUEST_NTLMv1 | 291 NEGOTIATE_FLAG_REQUEST_NTLM2_SESSION | 292 NEGOTIATE_FLAG_REQUEST_VERSION | 293 NEGOTIATE_FLAG_REQUEST_ALWAYS_SIGN | 294 NEGOTIATE_FLAG_REQUEST_128BIT_KEY_EXCH | 295 NEGOTIATE_FLAG_REQUEST_56BIT_ENCRYPTION | 296 NEGOTIATE_FLAG_REQUEST_UNICODE_ENCODING 297 298 nm.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} 299 return nm, nil 300 } 301 302 func (n *V2ClientSession) ProcessChallengeMessage(cm *ChallengeMessage) (err error) { 303 n.challengeMessage = cm 304 n.serverChallenge = cm.ServerChallenge 305 n.clientChallenge = randomBytes(8) 306 307 // Set up the default flags for processing the response. These are the flags that we will return 308 // in the authenticate message 309 flags := uint32(0) 310 flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) 311 flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags) 312 flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) 313 flags = NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags) 314 flags = NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags) 315 flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) 316 flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags) 317 flags = NTLMSSP_NEGOTIATE_DATAGRAM.Set(flags) 318 flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags) 319 flags = NTLMSSP_REQUEST_TARGET.Set(flags) 320 flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags) 321 flags = NTLMSSP_NEGOTIATE_128.Set(flags) 322 323 n.NegotiateFlags = flags 324 325 err = n.fetchResponseKeys() 326 if err != nil { 327 return err 328 } 329 330 timestamp := timeToWindowsFileTime(time.Now()) 331 err = n.computeExpectedResponses(timestamp, cm.TargetInfoPayloadStruct.Payload) 332 if err != nil { 333 return err 334 } 335 336 err = n.computeKeyExchangeKey() 337 if err != nil { 338 return err 339 } 340 341 err = n.computeEncryptedSessionKey() 342 if err != nil { 343 return err 344 } 345 346 // [Psiphon] 347 // Copied almost verbatim from ProcessAuthenticateMessage. We received a 348 // panic report due to a nil cm.Version. 349 if cm.Version == nil { 350 //UGH not entirely sure how this could possibly happen, going to put this in for now 351 //TODO investigate if this ever is really happening 352 cm.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} 353 354 } 355 356 err = n.calculateKeys(cm.Version.NTLMRevisionCurrent) 357 if err != nil { 358 return err 359 } 360 361 n.clientHandle, err = rc4Init(n.ClientSealingKey) 362 if err != nil { 363 return err 364 } 365 n.serverHandle, err = rc4Init(n.ServerSealingKey) 366 if err != nil { 367 return err 368 } 369 return nil 370 } 371 372 func (n *V2ClientSession) GenerateAuthenticateMessage() (am *AuthenticateMessage, err error) { 373 am = new(AuthenticateMessage) 374 am.Signature = []byte("NTLMSSP\x00") 375 am.MessageType = uint32(3) 376 am.LmChallengeResponse, _ = CreateBytePayload(n.lmChallengeResponse) 377 am.NtChallengeResponseFields, _ = CreateBytePayload(n.ntChallengeResponse) 378 am.DomainName, _ = CreateStringPayload(n.userDomain) 379 am.UserName, _ = CreateStringPayload(n.user) 380 381 // [Psiphon] 382 // Set a blank workstation value, which is less distinguishable than the previous hard-coded value. 383 // See also: https://github.com/Azure/go-ntlmssp/commit/5e29b886690f00c76b876ae9ab8e31e7c3509203. 384 385 am.Workstation, _ = CreateStringPayload("") 386 am.EncryptedRandomSessionKey, _ = CreateBytePayload(n.encryptedRandomSessionKey) 387 am.NegotiateFlags = n.NegotiateFlags 388 am.Mic = make([]byte, 16) 389 am.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: 0x0F} 390 return am, nil 391 } 392 393 func (n *V2ClientSession) computeEncryptedSessionKey() (err error) { 394 if NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.NegotiateFlags) { 395 n.exportedSessionKey = randomBytes(16) 396 n.encryptedRandomSessionKey, err = rc4K(n.keyExchangeKey, n.exportedSessionKey) 397 if err != nil { 398 return err 399 } 400 } else { 401 n.encryptedRandomSessionKey = n.keyExchangeKey 402 } 403 return nil 404 } 405 406 /******************************** 407 NTLM V2 Password hash functions 408 *********************************/ 409 410 // Define ntowfv2(Passwd, User, UserDom) as 411 func ntowfv2(user string, passwd string, userDom string) []byte { 412 concat := utf16FromString(strings.ToUpper(user) + userDom) 413 return hmacMd5(md4(utf16FromString(passwd)), concat) 414 } 415 416 // Define lmowfv2(Passwd, User, UserDom) as 417 func lmowfv2(user string, passwd string, userDom string) []byte { 418 return ntowfv2(user, passwd, userDom) 419 } 420 421 /******************************** 422 Helper functions 423 *********************************/ 424 425 func timeToWindowsFileTime(t time.Time) []byte { 426 var ll int64 427 ll = (int64(t.Unix()) * int64(10000000)) + int64(116444736000000000) 428 buffer := bytes.NewBuffer(make([]byte, 0, 8)) 429 binary.Write(buffer, binary.LittleEndian, ll) 430 return buffer.Bytes() 431 }