├── .gitignore ├── utils ├── decode_auth.go └── test_auth.go ├── ntlm ├── message_negotiate.go ├── negotiate_flags_test.go ├── helpers_test.go ├── version.go ├── md4 │ ├── LICENSE │ ├── md4block.go │ ├── md4.go │ └── md4_test.go ├── crypto_test.go ├── message_challenge_test.go ├── keys.go ├── helpers.go ├── payload.go ├── signature_test.go ├── ntlm.go ├── crypto.go ├── message_authenticate_test.go ├── signature.go ├── challenge_responses.go ├── message_challenge.go ├── av_pairs.go ├── ntlmv2_test.go ├── message_authenticate.go ├── ntlmv1_test.go ├── negotiate_flags.go ├── ntlmv1.go └── ntlmv2.go ├── License └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | pkg/ 3 | ======= 4 | pkg 5 | >>>>>>> adding License information and go format 6 | -------------------------------------------------------------------------------- /utils/decode_auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "flag" 6 | "fmt" 7 | 8 | "github.com/ThomsonReutersEikon/go-ntlm/ntlm" 9 | ) 10 | 11 | func main() { 12 | var ntlmVersion = flag.Int("ntlm", 2, "NTLM version to try: 1 or 2") 13 | flag.Parse() 14 | var data string 15 | fmt.Println("Paste the base64 encoded Authenticate message (with no line breaks):") 16 | fmt.Scanf("%s", &data) 17 | authenticateData, _ := base64.StdEncoding.DecodeString(data) 18 | a, _ := ntlm.ParseAuthenticateMessage(authenticateData, *ntlmVersion) 19 | fmt.Printf(a.String()) 20 | } 21 | -------------------------------------------------------------------------------- /ntlm/message_negotiate.go: -------------------------------------------------------------------------------- 1 | //Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information 2 | 3 | package ntlm 4 | 5 | type NegotiateMessage struct { 6 | // All bytes of the message 7 | Bytes []byte 8 | 9 | // sig - 8 bytes 10 | Signature []byte 11 | // message type - 4 bytes 12 | MessageType uint32 13 | // negotiate flags - 4bytes 14 | NegotiateFlags uint32 15 | // If the NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED flag is not set in NegotiateFlags, 16 | // indicating that no DomainName is supplied in Payload - then this should have Len 0 / MaxLen 0 17 | // this contains a domain name 18 | DomainNameFields *PayloadStruct 19 | // If the NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED flag is not set in NegotiateFlags, 20 | // indicating that no WorkstationName is supplied in Payload - then this should have Len 0 / MaxLen 0 21 | WorkstationFields *PayloadStruct 22 | // version - 8 bytes 23 | Version *VersionStruct 24 | // payload - variable 25 | Payload []byte 26 | PayloadOffset int 27 | } 28 | -------------------------------------------------------------------------------- /ntlm/negotiate_flags_test.go: -------------------------------------------------------------------------------- 1 | //Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information 2 | 3 | package ntlm 4 | 5 | import ( 6 | "encoding/binary" 7 | "encoding/hex" 8 | "testing" 9 | ) 10 | 11 | func TestFlags(t *testing.T) { 12 | // Sample value from 4.2.2 NTLM v1 Authentication 13 | bytes, _ := hex.DecodeString("338202e2") 14 | 15 | flags := uint32(0) 16 | flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) 17 | flags = NTLMSSP_NEGOTIATE_56.Set(flags) 18 | flags = NTLMSSP_NEGOTIATE_128.Set(flags) 19 | flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags) 20 | flags = NTLMSSP_TARGET_TYPE_SERVER.Set(flags) 21 | flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) 22 | flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags) 23 | flags = NTLMSSP_NEGOTIATE_SEAL.Set(flags) 24 | flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags) 25 | flags = NTLM_NEGOTIATE_OEM.Set(flags) 26 | flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags) 27 | 28 | if flags != binary.LittleEndian.Uint32(bytes) { 29 | t.Error("NTLM Flags are not correct") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ntlm/helpers_test.go: -------------------------------------------------------------------------------- 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/hex" 8 | "testing" 9 | ) 10 | 11 | func TestUTf16ToString(t *testing.T) { 12 | expected, _ := hex.DecodeString("5500730065007200") 13 | result := utf16FromString("User") 14 | if !bytes.Equal(expected, result) { 15 | t.Errorf("UTF16ToString failed got %s expected %s", hex.EncodeToString(result), "5500730065007200") 16 | } 17 | } 18 | 19 | func TestMacsEquals(t *testing.T) { 20 | // the MacsEqual should ignore the values in the second 4 bytes 21 | firstSlice := []byte{0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xf0, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff} 22 | secondSlice := []byte{0xf1, 0xf2, 0xf3, 0xf4, 0x00, 0x00, 0x00, 0x00, 0xf9, 0xf0, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff} 23 | if !MacsEqual(firstSlice, secondSlice) { 24 | t.Errorf("Expected MacsEqual(%v, %v) to be true", firstSlice, secondSlice) 25 | } 26 | } 27 | 28 | func TestMacsEqualsFail(t *testing.T) { 29 | // the last bytes in the following test case should cause MacsEqual to return false 30 | firstSlice := []byte{0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xf0, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff} 31 | secondSlice := []byte{0xf1, 0xf2, 0xf3, 0xf4, 0x00, 0x00, 0x00, 0x00, 0xf9, 0xf0, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xfe} 32 | if MacsEqual(firstSlice, secondSlice) { 33 | t.Errorf("Expected MacsEqual(%v, %v) to be false", firstSlice, secondSlice) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ntlm/version.go: -------------------------------------------------------------------------------- 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 | "fmt" 9 | ) 10 | 11 | type VersionStruct struct { 12 | ProductMajorVersion uint8 13 | ProductMinorVersion uint8 14 | ProductBuild uint16 15 | Reserved []byte 16 | NTLMRevisionCurrent uint8 17 | } 18 | 19 | func ReadVersionStruct(structSource []byte) (*VersionStruct, error) { 20 | versionStruct := new(VersionStruct) 21 | 22 | versionStruct.ProductMajorVersion = uint8(structSource[0]) 23 | versionStruct.ProductMinorVersion = uint8(structSource[1]) 24 | versionStruct.ProductBuild = binary.LittleEndian.Uint16(structSource[2:4]) 25 | versionStruct.Reserved = structSource[4:7] 26 | versionStruct.NTLMRevisionCurrent = uint8(structSource[7]) 27 | 28 | return versionStruct, nil 29 | } 30 | 31 | func (v *VersionStruct) String() string { 32 | return fmt.Sprintf("%d.%d.%d Ntlm %d", v.ProductMajorVersion, v.ProductMinorVersion, v.ProductBuild, v.NTLMRevisionCurrent) 33 | } 34 | 35 | func (v *VersionStruct) Bytes() []byte { 36 | dest := make([]byte, 0, 8) 37 | buffer := bytes.NewBuffer(dest) 38 | 39 | binary.Write(buffer, binary.LittleEndian, v.ProductMajorVersion) 40 | binary.Write(buffer, binary.LittleEndian, v.ProductMinorVersion) 41 | binary.Write(buffer, binary.LittleEndian, v.ProductBuild) 42 | buffer.Write(make([]byte, 3)) 43 | binary.Write(buffer, binary.LittleEndian, uint8(v.NTLMRevisionCurrent)) 44 | 45 | return buffer.Bytes() 46 | } 47 | -------------------------------------------------------------------------------- /ntlm/md4/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Thomson Reuters Global Resources 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 3. All advertising materials mentioning features or use of this software 12 | must display the following acknowledgement: 13 | This product includes software developed by the Thomson Reuters Global Resources. 14 | 4. Neither the name of the Thomson Reuters Global Resources nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY Thomson Reuters Global Resources ''AS IS'' AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL Thomson Reuters Global Resources BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /ntlm/crypto_test.go: -------------------------------------------------------------------------------- 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/hex" 8 | "testing" 9 | ) 10 | 11 | func TestMd4(t *testing.T) { 12 | data := []byte{1, 2, 3, 4, 5} 13 | byteData, _ := hex.DecodeString("93ebafdfedd1994e8018cc295cc1a8ee") 14 | if !bytes.Equal(md4(data), byteData) { 15 | t.Error("MD4 result not correct") 16 | } 17 | } 18 | 19 | func TestHmacMd5(t *testing.T) { 20 | data := []byte{1, 2, 3, 4, 5} 21 | byteData, _ := hex.DecodeString("9155578efbf3810a2adb4dee232a5fee") 22 | if !bytes.Equal(hmacMd5(data, data), byteData) { 23 | t.Error("HmacMd5 result not correct") 24 | } 25 | } 26 | 27 | func TestNonce(t *testing.T) { 28 | data := nonce(10) 29 | if len(data) != 10 { 30 | t.Error("Nonce is incorrect length") 31 | } 32 | } 33 | 34 | func TestRc4K(t *testing.T) { 35 | data := []byte{1, 2, 3, 4, 5} 36 | key := []byte{1, 2, 3, 4, 5} 37 | result, err := rc4K(key, data) 38 | if err != nil { 39 | // TODO: Need some sample data to test RC4K 40 | // t.Error("Error returned for RC4K") 41 | } 42 | if !bytes.Equal(result, data) { 43 | // t.Error("RC4K result not correct") 44 | } 45 | } 46 | 47 | func TestDesL(t *testing.T) { 48 | key, _ := hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d") 49 | message := []byte("12345678") 50 | result, _ := desL(key, message) 51 | expected, _ := hex.DecodeString("1192855D461A9754D189D8AE94D82488E3707C0662C0476A") 52 | if !bytes.Equal(result, expected) { 53 | t.Errorf("DesL did not produce correct result, got %s expected %s", hex.EncodeToString(result), hex.EncodeToString(expected)) 54 | } 55 | } 56 | 57 | func TestCRC32(t *testing.T) { 58 | bytes := []byte("Discard medicine more than two years old.") 59 | result := crc32(bytes) 60 | expected := uint32(0x6b9cdfe7) 61 | if expected != result { 62 | t.Errorf("CRC 32 data is not correct got %d expected %d", result, expected) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NTLM Implementation for Go 2 | 3 | This is a native implementation of NTLM for Go that was implemented using the Microsoft MS-NLMP documentation available at http://msdn.microsoft.com/en-us/library/cc236621.aspx. 4 | The library is currently in use and has been tested with connectionless NTLMv1 and v2 with and without extended session security. 5 | 6 | ## Usage Notes 7 | 8 | Currently the implementation only supports connectionless (datagram) oriented NTLM. We did not need connection oriented NTLM for our usage 9 | and so it is not implemented. However it should be extremely straightforward to implement connection oriented NTLM as all 10 | the operations required are present in the library. The major missing piece is the negotiation of capabilities between 11 | the client and the server, for our use we hardcoded a supported set of negotiation flags. 12 | 13 | ## Sample Usage as NTLM Client 14 | 15 | ```go 16 | import "github.com/ThomsonReutersEikon/go-ntlm/ntlm" 17 | 18 | session, err = ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionlessMode) 19 | session.SetUserInfo("someuser","somepassword","somedomain") 20 | 21 | negotiate := session.GenerateNegotiateMessage() 22 | 23 | 24 | 25 | challenge, err := ntlm.ParseChallengeMessage(challengeBytes) 26 | session.ProcessChallengeMessage(challenge) 27 | 28 | authenticate := session.GenerateAuthenticateMessage() 29 | 30 | 31 | ``` 32 | 33 | ## Sample Usage as NTLM Server 34 | 35 | ```go 36 | session, err := ntlm.CreateServerSession(ntlm.Version1, ntlm.ConnectionlessMode) 37 | session.SetUserInfo("someuser","somepassword","somedomain") 38 | 39 | challenge := session.GenerateChallengeMessage() 40 | 41 | 42 | 43 | 44 | 45 | auth, err := ntlm.ParseAuthenticateMessage(authenticateBytes) 46 | session.ProcessAuthenticateMessage(auth) 47 | ``` 48 | 49 | ## Generating a message MAC 50 | 51 | Once a session is created you can generate the Mac for a message using: 52 | 53 | ```go 54 | message := "this is some message to sign" 55 | sequenceNumber := 100 56 | signature, err := session.Mac([]byte(message), sequenceNumber) 57 | ``` 58 | 59 | ## License 60 | Copyright Thomson Reuters Global Resources 2013 61 | Apache License 62 | -------------------------------------------------------------------------------- /ntlm/md4/md4block.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // MD4 block step. 6 | // In its own file so that a faster assembly or C version 7 | // can be substituted easily. 8 | 9 | package md4 10 | 11 | var shift1 = []uint{3, 7, 11, 19} 12 | var shift2 = []uint{3, 5, 9, 13} 13 | var shift3 = []uint{3, 9, 11, 15} 14 | 15 | var xIndex2 = []uint{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15} 16 | var xIndex3 = []uint{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15} 17 | 18 | func _Block(dig *digest, p []byte) int { 19 | a := dig.s[0] 20 | b := dig.s[1] 21 | c := dig.s[2] 22 | d := dig.s[3] 23 | n := 0 24 | var X [16]uint32 25 | for len(p) >= _Chunk { 26 | aa, bb, cc, dd := a, b, c, d 27 | 28 | j := 0 29 | for i := 0; i < 16; i++ { 30 | X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24 31 | j += 4 32 | } 33 | 34 | // If this needs to be made faster in the future, 35 | // the usual trick is to unroll each of these 36 | // loops by a factor of 4; that lets you replace 37 | // the shift[] lookups with constants and, 38 | // with suitable variable renaming in each 39 | // unrolled body, delete the a, b, c, d = d, a, b, c 40 | // (or you can let the optimizer do the renaming). 41 | // 42 | // The index variables are uint so that % by a power 43 | // of two can be optimized easily by a compiler. 44 | 45 | // Round 1. 46 | for i := uint(0); i < 16; i++ { 47 | x := i 48 | s := shift1[i%4] 49 | f := ((c ^ d) & b) ^ d 50 | a += f + X[x] 51 | a = a<>(32-s) 52 | a, b, c, d = d, a, b, c 53 | } 54 | 55 | // Round 2. 56 | for i := uint(0); i < 16; i++ { 57 | x := xIndex2[i] 58 | s := shift2[i%4] 59 | g := (b & c) | (b & d) | (c & d) 60 | a += g + X[x] + 0x5a827999 61 | a = a<>(32-s) 62 | a, b, c, d = d, a, b, c 63 | } 64 | 65 | // Round 3. 66 | for i := uint(0); i < 16; i++ { 67 | x := xIndex3[i] 68 | s := shift3[i%4] 69 | h := b ^ c ^ d 70 | a += h + X[x] + 0x6ed9eba1 71 | a = a<>(32-s) 72 | a, b, c, d = d, a, b, c 73 | } 74 | 75 | a += aa 76 | b += bb 77 | c += cc 78 | d += dd 79 | 80 | p = p[_Chunk:] 81 | n += _Chunk 82 | } 83 | 84 | dig.s[0] = a 85 | dig.s[1] = b 86 | dig.s[2] = c 87 | dig.s[3] = d 88 | return n 89 | } 90 | -------------------------------------------------------------------------------- /ntlm/message_challenge_test.go: -------------------------------------------------------------------------------- 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/base64" 8 | "encoding/hex" 9 | "fmt" 10 | "testing" 11 | ) 12 | 13 | func TestDecodeChallenge(t *testing.T) { 14 | challengeMessage := "TlRMTVNTUAACAAAAAAAAADgAAADzgpjiuaopAbx9ejQAAAAAAAAAAKIAogA4AAAABQLODgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA=" 15 | challengeData, err := base64.StdEncoding.DecodeString(challengeMessage) 16 | 17 | if err != nil { 18 | t.Error("Could not base64 decode message data") 19 | } 20 | 21 | challenge, err := ParseChallengeMessage(challengeData) 22 | 23 | if err != nil || challenge == nil { 24 | t.Error("Failed to parse challenge message " + err.Error()) 25 | } 26 | 27 | if challenge.TargetName.Len != 0 || challenge.TargetName.MaxLen != 0 || challenge.TargetName.Offset != 56 { 28 | values := fmt.Sprintf("TargetName Len:%v MaxLen:%v Offset:%v", challenge.TargetName.Len, challenge.TargetName.MaxLen, challenge.TargetName.Offset) 29 | t.Error("Failed to parse Target Name in challenge: " + values) 30 | } 31 | 32 | if challenge.NegotiateFlags != uint32(3801645811) { 33 | t.Errorf("Challenge negotiate flags not correct should be %v got %d", uint32(3801645811), challenge.NegotiateFlags) 34 | } 35 | 36 | serverChallenge, err := hex.DecodeString("B9AA2901BC7D7A34") 37 | if !bytes.Equal(challenge.ServerChallenge, serverChallenge) { 38 | hex := hex.EncodeToString(challenge.ServerChallenge) 39 | t.Error("Server challenge is not correct '" + hex + "'") 40 | } 41 | 42 | if challenge.Version.ProductMajorVersion != 5 || challenge.Version.ProductMinorVersion != 2 || challenge.Version.ProductBuild != 3790 || challenge.Version.NTLMRevisionCurrent != 15 { 43 | t.Error("Version information is not correct: '" + challenge.Version.String() + "'") 44 | } 45 | 46 | if len(challenge.Payload) != int(challenge.TargetInfoPayloadStruct.Len) { 47 | t.Error("Payload length is not long enough") 48 | } 49 | 50 | challenge.String() 51 | 52 | outBytes := challenge.Bytes() 53 | 54 | if len(outBytes) > 0 { 55 | reparsed, err := ParseChallengeMessage(outBytes) 56 | if err != nil { 57 | t.Error("Could not re-parse challenge message") 58 | } 59 | if reparsed.String() != challenge.String() { 60 | t.Error("Reparsed message is not the same") 61 | } 62 | } else { 63 | t.Error("Invalid challenge messsage bytes") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ntlm/keys.go: -------------------------------------------------------------------------------- 1 | //Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information 2 | 3 | package ntlm 4 | 5 | // Define KXKEY(SessionBaseKey, LmChallengeResponse, ServerChallenge) as 6 | func kxKey(flags uint32, sessionBaseKey []byte, lmChallengeResponse []byte, serverChallenge []byte, lmnowf []byte) (keyExchangeKey []byte, err error) { 7 | if NTLMSSP_NEGOTIATE_LM_KEY.IsSet(flags) { 8 | var part1, part2 []byte 9 | part1, err = des(lmnowf[0:7], lmChallengeResponse[0:8]) 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | key := append([]byte{lmnowf[7]}, []byte{0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD}...) 15 | part2, err = des(key, lmChallengeResponse[0:8]) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | keyExchangeKey = concat(part1, part2) 21 | } else if NTLMSSP_REQUEST_NON_NT_SESSION_KEY.IsSet(flags) { 22 | keyExchangeKey = concat(lmnowf[0:8], zeroBytes(8)) 23 | } else { 24 | keyExchangeKey = sessionBaseKey 25 | } 26 | 27 | return 28 | } 29 | 30 | // Define SIGNKEY(NegFlg, RandomSessionKey, Mode) as 31 | func signKey(flags uint32, randomSessionKey []byte, mode string) (signKey []byte) { 32 | if NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(flags) { 33 | if mode == "Client" { 34 | signKey = md5(concat(randomSessionKey, []byte("session key to client-to-server signing key magic constant\x00"))) 35 | } else { 36 | signKey = md5(concat(randomSessionKey, []byte("session key to server-to-client signing key magic constant\x00"))) 37 | } 38 | } else { 39 | signKey = nil 40 | } 41 | return 42 | } 43 | 44 | // Define SEALKEY(NegotiateFlags, RandomSessionKey, Mode) as 45 | func sealKey(flags uint32, randomSessionKey []byte, mode string) (sealKey []byte) { 46 | if NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(flags) { 47 | if NTLMSSP_NEGOTIATE_128.IsSet(flags) { 48 | sealKey = randomSessionKey 49 | } else if NTLMSSP_NEGOTIATE_56.IsSet(flags) { 50 | sealKey = randomSessionKey[0:7] 51 | } else { 52 | sealKey = randomSessionKey[0:5] 53 | } 54 | if mode == "Client" { 55 | sealKey = md5(concat(sealKey, []byte("session key to client-to-server sealing key magic constant\x00"))) 56 | } else { 57 | sealKey = md5(concat(sealKey, []byte("session key to server-to-client sealing key magic constant\x00"))) 58 | } 59 | } else if NTLMSSP_NEGOTIATE_LM_KEY.IsSet(flags) { 60 | if NTLMSSP_NEGOTIATE_56.IsSet(flags) { 61 | sealKey = concat(randomSessionKey[0:7], []byte{0xA0}) 62 | } else { 63 | sealKey = concat(randomSessionKey[0:5], []byte{0xE5, 0x38, 0xB0}) 64 | } 65 | } else { 66 | sealKey = randomSessionKey 67 | } 68 | 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /ntlm/helpers.go: -------------------------------------------------------------------------------- 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 | "crypto/rand" 8 | "encoding/binary" 9 | "unicode/utf16" 10 | ) 11 | 12 | // Concatenate two byte slices into a new slice 13 | func concat(ar ...[]byte) []byte { 14 | return bytes.Join(ar, nil) 15 | } 16 | 17 | // Create a 0 initialized slice of bytes 18 | func zeroBytes(length int) []byte { 19 | return make([]byte, length, length) 20 | } 21 | 22 | func randomBytes(length int) []byte { 23 | randombytes := make([]byte, length) 24 | _, err := rand.Read(randombytes) 25 | if err != nil { 26 | } // TODO: What to do with err here 27 | return randombytes 28 | } 29 | 30 | // Zero pad the input byte slice to the given size 31 | // bytes - input byte slice 32 | // offset - where to start taking the bytes from the input slice 33 | // size - size of the output byte slize 34 | func zeroPaddedBytes(bytes []byte, offset int, size int) []byte { 35 | newSlice := zeroBytes(size) 36 | for i := 0; i < size && i+offset < len(bytes); i++ { 37 | newSlice[i] = bytes[i+offset] 38 | } 39 | return newSlice 40 | } 41 | 42 | func MacsEqual(slice1, slice2 []byte) bool { 43 | if len(slice1) != len(slice2) { 44 | return false 45 | } 46 | for i := 0; i < len(slice1); i++ { 47 | // bytes between 4 and 7 (inclusive) contains random 48 | // data that should be ignored while comparing the 49 | // macs 50 | if (i < 4 || i > 7) && slice1[i] != slice2[i] { 51 | return false 52 | } 53 | } 54 | return true 55 | } 56 | 57 | func utf16FromString(s string) []byte { 58 | encoded := utf16.Encode([]rune(s)) 59 | // TODO: I'm sure there is an easier way to do the conversion from utf16 to bytes 60 | result := zeroBytes(len(encoded) * 2) 61 | for i := 0; i < len(encoded); i++ { 62 | result[i*2] = byte(encoded[i]) 63 | result[i*2+1] = byte(encoded[i] << 8) 64 | } 65 | return result 66 | } 67 | 68 | // Convert a UTF16 string to UTF8 string for Go usage 69 | func utf16ToString(bytes []byte) string { 70 | var data []uint16 71 | 72 | // NOTE: This is definitely not the best way to do this, but when I tried using a buffer.Read I could not get it to work 73 | for offset := 0; offset < len(bytes); offset = offset + 2 { 74 | i := binary.LittleEndian.Uint16(bytes[offset : offset+2]) 75 | data = append(data, i) 76 | } 77 | 78 | return string(utf16.Decode(data)) 79 | } 80 | 81 | func uint32ToBytes(v uint32) []byte { 82 | bytes := make([]byte, 4) 83 | bytes[0] = byte(v & 0xff) 84 | bytes[1] = byte((v >> 8) & 0xff) 85 | bytes[2] = byte((v >> 16) & 0xff) 86 | bytes[3] = byte((v >> 24) & 0xff) 87 | return bytes 88 | } 89 | -------------------------------------------------------------------------------- /ntlm/md4/md4.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package md4 implements the MD4 hash algorithm as defined in RFC 1320. 6 | package md4 7 | 8 | import ( 9 | "crypto" 10 | "hash" 11 | ) 12 | 13 | func init() { 14 | crypto.RegisterHash(crypto.MD4, New) 15 | } 16 | 17 | // The size of an MD4 checksum in bytes. 18 | const Size = 16 19 | 20 | // The blocksize of MD4 in bytes. 21 | const BlockSize = 64 22 | 23 | const ( 24 | _Chunk = 64 25 | _Init0 = 0x67452301 26 | _Init1 = 0xEFCDAB89 27 | _Init2 = 0x98BADCFE 28 | _Init3 = 0x10325476 29 | ) 30 | 31 | // digest represents the partial evaluation of a checksum. 32 | type digest struct { 33 | s [4]uint32 34 | x [_Chunk]byte 35 | nx int 36 | len uint64 37 | } 38 | 39 | func (d *digest) Reset() { 40 | d.s[0] = _Init0 41 | d.s[1] = _Init1 42 | d.s[2] = _Init2 43 | d.s[3] = _Init3 44 | d.nx = 0 45 | d.len = 0 46 | } 47 | 48 | // New returns a new hash.Hash computing the MD4 checksum. 49 | func New() hash.Hash { 50 | d := new(digest) 51 | d.Reset() 52 | return d 53 | } 54 | 55 | func (d *digest) Size() int { return Size } 56 | 57 | func (d *digest) BlockSize() int { return BlockSize } 58 | 59 | func (d *digest) Write(p []byte) (nn int, err error) { 60 | nn = len(p) 61 | d.len += uint64(nn) 62 | if d.nx > 0 { 63 | n := len(p) 64 | if n > _Chunk-d.nx { 65 | n = _Chunk - d.nx 66 | } 67 | for i := 0; i < n; i++ { 68 | d.x[d.nx+i] = p[i] 69 | } 70 | d.nx += n 71 | if d.nx == _Chunk { 72 | _Block(d, d.x[0:]) 73 | d.nx = 0 74 | } 75 | p = p[n:] 76 | } 77 | n := _Block(d, p) 78 | p = p[n:] 79 | if len(p) > 0 { 80 | d.nx = copy(d.x[:], p) 81 | } 82 | return 83 | } 84 | 85 | func (d0 *digest) Sum(in []byte) []byte { 86 | // Make a copy of d0, so that caller can keep writing and summing. 87 | d := new(digest) 88 | *d = *d0 89 | 90 | // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. 91 | len := d.len 92 | var tmp [64]byte 93 | tmp[0] = 0x80 94 | if len%64 < 56 { 95 | d.Write(tmp[0 : 56-len%64]) 96 | } else { 97 | d.Write(tmp[0 : 64+56-len%64]) 98 | } 99 | 100 | // Length in bits. 101 | len <<= 3 102 | for i := uint(0); i < 8; i++ { 103 | tmp[i] = byte(len >> (8 * i)) 104 | } 105 | d.Write(tmp[0:8]) 106 | 107 | if d.nx != 0 { 108 | panic("d.nx != 0") 109 | } 110 | 111 | for _, s := range d.s { 112 | in = append(in, byte(s>>0)) 113 | in = append(in, byte(s>>8)) 114 | in = append(in, byte(s>>16)) 115 | in = append(in, byte(s>>24)) 116 | } 117 | return in 118 | } 119 | -------------------------------------------------------------------------------- /ntlm/payload.go: -------------------------------------------------------------------------------- 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 | ) 10 | 11 | const ( 12 | UnicodeStringPayload = iota 13 | OemStringPayload 14 | BytesPayload 15 | ) 16 | 17 | type PayloadStruct struct { 18 | Type int 19 | Len uint16 20 | MaxLen uint16 21 | Offset uint32 22 | Payload []byte 23 | } 24 | 25 | func (p *PayloadStruct) Bytes() []byte { 26 | dest := make([]byte, 0, 8) 27 | buffer := bytes.NewBuffer(dest) 28 | 29 | binary.Write(buffer, binary.LittleEndian, p.Len) 30 | binary.Write(buffer, binary.LittleEndian, p.MaxLen) 31 | binary.Write(buffer, binary.LittleEndian, p.Offset) 32 | 33 | return buffer.Bytes() 34 | } 35 | 36 | func (p *PayloadStruct) String() string { 37 | var returnString string 38 | 39 | switch p.Type { 40 | case UnicodeStringPayload: 41 | returnString = utf16ToString(p.Payload) 42 | case OemStringPayload: 43 | returnString = string(p.Payload) 44 | case BytesPayload: 45 | returnString = hex.EncodeToString(p.Payload) 46 | default: 47 | returnString = "unknown type" 48 | } 49 | return returnString 50 | } 51 | 52 | func CreateBytePayload(bytes []byte) (*PayloadStruct, error) { 53 | p := new(PayloadStruct) 54 | p.Type = BytesPayload 55 | p.Len = uint16(len(bytes)) 56 | p.MaxLen = uint16(len(bytes)) 57 | p.Payload = bytes // TODO: Copy these bytes instead of keeping a reference 58 | return p, nil 59 | } 60 | 61 | func CreateStringPayload(value string) (*PayloadStruct, error) { 62 | // Create UTF16 unicode bytes from string 63 | bytes := utf16FromString(value) 64 | p := new(PayloadStruct) 65 | p.Type = UnicodeStringPayload 66 | p.Len = uint16(len(bytes)) 67 | p.MaxLen = uint16(len(bytes)) 68 | p.Payload = bytes // TODO: Copy these bytes instead of keeping a reference 69 | return p, nil 70 | } 71 | 72 | func ReadStringPayload(startByte int, bytes []byte) (*PayloadStruct, error) { 73 | return ReadPayloadStruct(startByte, bytes, UnicodeStringPayload) 74 | } 75 | 76 | func ReadBytePayload(startByte int, bytes []byte) (*PayloadStruct, error) { 77 | return ReadPayloadStruct(startByte, bytes, BytesPayload) 78 | } 79 | 80 | func ReadPayloadStruct(startByte int, bytes []byte, PayloadType int) (*PayloadStruct, error) { 81 | p := new(PayloadStruct) 82 | 83 | p.Type = PayloadType 84 | p.Len = binary.LittleEndian.Uint16(bytes[startByte : startByte+2]) 85 | p.MaxLen = binary.LittleEndian.Uint16(bytes[startByte+2 : startByte+4]) 86 | p.Offset = binary.LittleEndian.Uint32(bytes[startByte+4 : startByte+8]) 87 | 88 | if p.Len > 0 { 89 | endOffset := p.Offset + uint32(p.Len) 90 | p.Payload = bytes[p.Offset:endOffset] 91 | } 92 | 93 | return p, nil 94 | } 95 | -------------------------------------------------------------------------------- /ntlm/signature_test.go: -------------------------------------------------------------------------------- 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/hex" 8 | "testing" 9 | ) 10 | 11 | func checkSigValue(t *testing.T, name string, value []byte, expected string, err error) { 12 | if err != nil { 13 | t.Errorf("Signature %s received error: %s", name, err) 14 | } else { 15 | expectedBytes, _ := hex.DecodeString(expected) 16 | if !bytes.Equal(expectedBytes, value) { 17 | t.Errorf("Signature %s is not correct got %s expected %s", name, hex.EncodeToString(value), expected) 18 | } 19 | } 20 | } 21 | 22 | // 4.2.2.4 GSS_WrapEx Examples 23 | func TestSealWithoutExtendedSessionSecurity(t *testing.T) { 24 | key, _ := hex.DecodeString("55555555555555555555555555555555") 25 | handle, _ := rc4Init(key) 26 | plaintext, _ := hex.DecodeString("50006c00610069006e007400650078007400") 27 | seqNum := uint32(0) 28 | flags := uint32(0) 29 | 30 | sealed, sig := seal(flags, handle, nil, seqNum, plaintext) 31 | checkSigValue(t, "Sealed message", sealed, "56fe04d861f9319af0d7238a2e3b4d457fb8", nil) 32 | checkSigValue(t, "Randompad", sig.RandomPad, "00000000", nil) 33 | checkSigValue(t, "RC4 Checksum", sig.CheckSum, "09dcd1df", nil) 34 | checkSigValue(t, "Xor Seq", sig.SeqNum, "2e459d36", nil) 35 | } 36 | 37 | func TestSealSignWithExtendedSessionSecurity(t *testing.T) { 38 | sealKey, _ := hex.DecodeString("04dd7f014d8504d265a25cc86a3a7c06") 39 | signKey, _ := hex.DecodeString("60e799be5c72fc92922ae8ebe961fb8d") 40 | handle, _ := rc4Init(sealKey) 41 | plaintext, _ := hex.DecodeString("50006c00610069006e007400650078007400") 42 | seqNum := uint32(0) 43 | flags := uint32(0) 44 | flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) 45 | 46 | sealed, sig := seal(flags, handle, signKey, seqNum, plaintext) 47 | checkSigValue(t, "Sealed Data", sealed, "a02372f6530273f3aa1eb90190ce5200c99d", nil) 48 | checkSigValue(t, "CheckSum", sig.CheckSum, "ff2aeb52f681793a", nil) 49 | checkSigValue(t, "Signature", sig.Bytes(), "01000000ff2aeb52f681793a00000000", nil) 50 | } 51 | 52 | func TestSealSignWithExtendedSessionSecurityKeyEx(t *testing.T) { 53 | sealKey, _ := hex.DecodeString("59f600973cc4960a25480a7c196e4c58") 54 | signKey, _ := hex.DecodeString("4788dc861b4782f35d43fd98fe1a2d39") 55 | handle, _ := rc4Init(sealKey) 56 | plaintext, _ := hex.DecodeString("50006c00610069006e007400650078007400") 57 | seqNum := uint32(0) 58 | flags := uint32(0) 59 | flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) 60 | flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) 61 | 62 | sealed, sig := seal(flags, handle, signKey, seqNum, plaintext) 63 | checkSigValue(t, "Sealed Data", sealed, "54e50165bf1936dc996020c1811b0f06fb5f", nil) 64 | checkSigValue(t, "RC4 CheckSum", sig.CheckSum, "7fb38ec5c55d4976", nil) 65 | checkSigValue(t, "Signature", sig.Bytes(), "010000007fb38ec5c55d497600000000", nil) 66 | } 67 | -------------------------------------------------------------------------------- /ntlm/md4/md4_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package md4 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "testing" 11 | ) 12 | 13 | type md4Test struct { 14 | out string 15 | in string 16 | } 17 | 18 | var golden = []md4Test{ 19 | {"31d6cfe0d16ae931b73c59d7e0c089c0", ""}, 20 | {"bde52cb31de33e46245e05fbdbd6fb24", "a"}, 21 | {"ec388dd78999dfc7cf4632465693b6bf", "ab"}, 22 | {"a448017aaf21d8525fc10ae87aa6729d", "abc"}, 23 | {"41decd8f579255c5200f86a4bb3ba740", "abcd"}, 24 | {"9803f4a34e8eb14f96adba49064a0c41", "abcde"}, 25 | {"804e7f1c2586e50b49ac65db5b645131", "abcdef"}, 26 | {"752f4adfe53d1da0241b5bc216d098fc", "abcdefg"}, 27 | {"ad9daf8d49d81988590a6f0e745d15dd", "abcdefgh"}, 28 | {"1e4e28b05464316b56402b3815ed2dfd", "abcdefghi"}, 29 | {"dc959c6f5d6f9e04e4380777cc964b3d", "abcdefghij"}, 30 | {"1b5701e265778898ef7de5623bbe7cc0", "Discard medicine more than two years old."}, 31 | {"d7f087e090fe7ad4a01cb59dacc9a572", "He who has a shady past knows that nice guys finish last."}, 32 | {"a6f8fd6df617c72837592fc3570595c9", "I wouldn't marry him with a ten foot pole."}, 33 | {"c92a84a9526da8abc240c05d6b1a1ce0", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"}, 34 | {"f6013160c4dcb00847069fee3bb09803", "The days of the digital watch are numbered. -Tom Stoppard"}, 35 | {"2c3bb64f50b9107ed57640fe94bec09f", "Nepal premier won't resign."}, 36 | {"45b7d8a32c7806f2f7f897332774d6e4", "For every action there is an equal and opposite government program."}, 37 | {"b5b4f9026b175c62d7654bdc3a1cd438", "His money is twice tainted: 'taint yours and 'taint mine."}, 38 | {"caf44e80f2c20ce19b5ba1cab766e7bd", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"}, 39 | {"191fae6707f496aa54a6bce9f2ecf74d", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"}, 40 | {"9ddc753e7a4ccee6081cd1b45b23a834", "size: a.out: bad magic"}, 41 | {"8d050f55b1cadb9323474564be08a521", "The major problem is with sendmail. -Mark Horton"}, 42 | {"ad6e2587f74c3e3cc19146f6127fa2e3", "Give me a rock, paper and scissors and I will move the world. CCFestoon"}, 43 | {"1d616d60a5fabe85589c3f1566ca7fca", "If the enemy is within range, then so are you."}, 44 | {"aec3326a4f496a2ced65a1963f84577f", "It's well we cannot hear the screams/That we create in others' dreams."}, 45 | {"77b4fd762d6b9245e61c50bf6ebf118b", "You remind me of a TV show, but that's all right: I watch it anyway."}, 46 | {"e8f48c726bae5e516f6ddb1a4fe62438", "C is as portable as Stonehedge!!"}, 47 | {"a3a84366e7219e887423b01f9be7166e", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"}, 48 | {"a6b7aa35157e984ef5d9b7f32e5fbb52", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"}, 49 | {"75661f0545955f8f9abeeb17845f3fd6", "How can you write a big system without C++? -Paul Glick"}, 50 | } 51 | 52 | func TestGolden(t *testing.T) { 53 | for i := 0; i < len(golden); i++ { 54 | g := golden[i] 55 | c := New() 56 | for j := 0; j < 3; j++ { 57 | if j < 2 { 58 | io.WriteString(c, g.in) 59 | } else { 60 | io.WriteString(c, g.in[0:len(g.in)/2]) 61 | c.Sum(nil) 62 | io.WriteString(c, g.in[len(g.in)/2:]) 63 | } 64 | s := fmt.Sprintf("%x", c.Sum(nil)) 65 | if s != g.out { 66 | t.Fatalf("md4[%d](%s) = %s want %s", j, g.in, s, g.out) 67 | } 68 | c.Reset() 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ntlm/ntlm.go: -------------------------------------------------------------------------------- 1 | //Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information 2 | 3 | // Package NTLM implements the interfaces used for interacting with NTLMv1 and NTLMv2. 4 | // To create NTLM v1 or v2 sessions you would use CreateClientSession and create ClientServerSession. 5 | package ntlm 6 | 7 | import ( 8 | rc4P "crypto/rc4" 9 | "errors" 10 | ) 11 | 12 | type Version int 13 | 14 | const ( 15 | Version1 Version = 1 16 | Version2 Version = 2 17 | ) 18 | 19 | type Mode int 20 | 21 | const ( 22 | ConnectionlessMode Mode = iota 23 | ConnectionOrientedMode 24 | ) 25 | 26 | // Creates an NTLM v1 or v2 client 27 | // mode - This must be ConnectionlessMode or ConnectionOrientedMode depending on what type of NTLM is used 28 | // version - This must be Version1 or Version2 depending on the version of NTLM used 29 | func CreateClientSession(version Version, mode Mode) (n ClientSession, err error) { 30 | switch version { 31 | case Version1: 32 | n = new(V1ClientSession) 33 | case Version2: 34 | n = new(V2ClientSession) 35 | default: 36 | return nil, errors.New("Unknown NTLM Version, must be 1 or 2") 37 | } 38 | 39 | return n, nil 40 | } 41 | 42 | type ClientSession interface { 43 | SetUserInfo(username string, password string, domain string) 44 | SetMode(mode Mode) 45 | 46 | GenerateNegotiateMessage() (*NegotiateMessage, error) 47 | ProcessChallengeMessage(*ChallengeMessage) error 48 | GenerateAuthenticateMessage() (*AuthenticateMessage, error) 49 | 50 | Seal(message []byte) ([]byte, error) 51 | Sign(message []byte) ([]byte, error) 52 | Mac(message []byte, sequenceNumber int) ([]byte, error) 53 | VerifyMac(message, expectedMac []byte, sequenceNumber int) (bool, error) 54 | } 55 | 56 | // Creates an NTLM v1 or v2 server 57 | // mode - This must be ConnectionlessMode or ConnectionOrientedMode depending on what type of NTLM is used 58 | // version - This must be Version1 or Version2 depending on the version of NTLM used 59 | func CreateServerSession(version Version, mode Mode) (n ServerSession, err error) { 60 | switch version { 61 | case Version1: 62 | n = new(V1ServerSession) 63 | case Version2: 64 | n = new(V2ServerSession) 65 | default: 66 | return nil, errors.New("Unknown NTLM Version, must be 1 or 2") 67 | } 68 | 69 | n.SetMode(mode) 70 | return n, nil 71 | } 72 | 73 | type ServerSession interface { 74 | SetUserInfo(username string, password string, domain string) 75 | GetUserInfo() (string, string, string) 76 | 77 | SetMode(mode Mode) 78 | SetServerChallenge(challenge []byte) 79 | 80 | ProcessNegotiateMessage(*NegotiateMessage) error 81 | GenerateChallengeMessage() (*ChallengeMessage, error) 82 | ProcessAuthenticateMessage(*AuthenticateMessage) error 83 | 84 | GetSessionData() *SessionData 85 | 86 | Version() int 87 | Seal(message []byte) ([]byte, error) 88 | Sign(message []byte) ([]byte, error) 89 | Mac(message []byte, sequenceNumber int) ([]byte, error) 90 | VerifyMac(message, expectedMac []byte, sequenceNumber int) (bool, error) 91 | } 92 | 93 | // This struct collects NTLM data structures and keys that are used across all types of NTLM requests 94 | type SessionData struct { 95 | mode Mode 96 | 97 | user string 98 | password string 99 | userDomain string 100 | 101 | NegotiateFlags uint32 102 | 103 | negotiateMessage *NegotiateMessage 104 | challengeMessage *ChallengeMessage 105 | authenticateMessage *AuthenticateMessage 106 | 107 | serverChallenge []byte 108 | clientChallenge []byte 109 | ntChallengeResponse []byte 110 | lmChallengeResponse []byte 111 | 112 | responseKeyLM []byte 113 | responseKeyNT []byte 114 | exportedSessionKey []byte 115 | encryptedRandomSessionKey []byte 116 | keyExchangeKey []byte 117 | sessionBaseKey []byte 118 | mic []byte 119 | 120 | ClientSigningKey []byte 121 | ServerSigningKey []byte 122 | ClientSealingKey []byte 123 | ServerSealingKey []byte 124 | 125 | clientHandle *rc4P.Cipher 126 | serverHandle *rc4P.Cipher 127 | } 128 | -------------------------------------------------------------------------------- /ntlm/crypto.go: -------------------------------------------------------------------------------- 1 | //Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information 2 | 3 | package ntlm 4 | 5 | import ( 6 | desP "crypto/des" 7 | hmacP "crypto/hmac" 8 | md5P "crypto/md5" 9 | "crypto/rand" 10 | rc4P "crypto/rc4" 11 | crc32P "hash/crc32" 12 | 13 | md4P "github.com/ThomsonReutersEikon/go-ntlm/ntlm/md4" 14 | ) 15 | 16 | func md4(data []byte) []byte { 17 | md4 := md4P.New() 18 | md4.Write(data) 19 | return md4.Sum(nil) 20 | } 21 | 22 | func md5(data []byte) []byte { 23 | md5 := md5P.New() 24 | md5.Write(data) 25 | return md5.Sum(nil) 26 | } 27 | 28 | // Indicates the computation of a 16-byte HMAC-keyed MD5 message digest of the byte string M using the key K. 29 | func hmacMd5(key []byte, data []byte) []byte { 30 | mac := hmacP.New(md5P.New, key) 31 | mac.Write(data) 32 | return mac.Sum(nil) 33 | } 34 | 35 | // Indicates the computation of an N-byte cryptographic- strength random number. 36 | func nonce(length int) []byte { 37 | result := make([]byte, length) 38 | rand.Read(result) 39 | return result 40 | } 41 | 42 | func crc32(bytes []byte) uint32 { 43 | crc := crc32P.New(crc32P.IEEETable) 44 | crc.Write(bytes) 45 | return crc.Sum32() 46 | } 47 | 48 | // Indicates the encryption of data item D with the key K using the RC4 algorithm. 49 | func rc4K(key []byte, ciphertext []byte) ([]byte, error) { 50 | cipher, err := rc4P.NewCipher(key) 51 | if err != nil { 52 | return nil, err 53 | } 54 | result := make([]byte, len(ciphertext)) 55 | cipher.XORKeyStream(result, ciphertext) 56 | return result, nil 57 | } 58 | 59 | func rc4Init(key []byte) (cipher *rc4P.Cipher, err error) { 60 | cipher, err = rc4P.NewCipher(key) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return cipher, nil 65 | } 66 | 67 | func rc4(cipher *rc4P.Cipher, ciphertext []byte) []byte { 68 | result := make([]byte, len(ciphertext)) 69 | cipher.XORKeyStream(result, ciphertext) 70 | return result 71 | } 72 | 73 | // Indicates the encryption of an 8-byte data item D with the 7-byte key K using the Data Encryption Standard (DES) 74 | // algorithm in Electronic Codebook (ECB) mode. The result is 8 bytes in length ([FIPS46-2]). 75 | func des(key []byte, ciphertext []byte) ([]byte, error) { 76 | calcKey := createDesKey(key) 77 | cipher, err := desP.NewCipher(calcKey) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | result := make([]byte, len(ciphertext)) 83 | cipher.Encrypt(result, ciphertext) 84 | 85 | return result, nil 86 | } 87 | 88 | // Indicates the encryption of an 8-byte data item D with the 16-byte key K using the Data Encryption Standard Long (DESL) algorithm. 89 | // The result is 24 bytes in length. DESL(K, D) is computed as follows. 90 | // Note K[] implies a key represented as a character array. 91 | func desL(key []byte, cipherText []byte) ([]byte, error) { 92 | out1, err := des(zeroPaddedBytes(key, 0, 7), cipherText) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | out2, err := des(zeroPaddedBytes(key, 7, 7), cipherText) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | out3, err := des(zeroPaddedBytes(key, 14, 7), cipherText) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | return concat(out1, out2, out3), nil 108 | } 109 | 110 | // Creates a DES encryption key from the given 7 byte key material. 111 | func createDesKey(keyBytes []byte) []byte { 112 | material := zeroBytes(8) 113 | material[0] = keyBytes[0] 114 | material[1] = (byte)(keyBytes[0]<<7 | (keyBytes[1]&0xff)>>1) 115 | material[2] = (byte)(keyBytes[1]<<6 | (keyBytes[2]&0xff)>>2) 116 | material[3] = (byte)(keyBytes[2]<<5 | (keyBytes[3]&0xff)>>3) 117 | material[4] = (byte)(keyBytes[3]<<4 | (keyBytes[4]&0xff)>>4) 118 | material[5] = (byte)(keyBytes[4]<<3 | (keyBytes[5]&0xff)>>5) 119 | material[6] = (byte)(keyBytes[5]<<2 | (keyBytes[6]&0xff)>>6) 120 | material[7] = (byte)(keyBytes[6] << 1) 121 | oddParity(material) 122 | return material 123 | } 124 | 125 | // Applies odd parity to the given byte array. 126 | func oddParity(bytes []byte) { 127 | for i := 0; i < len(bytes); i++ { 128 | b := bytes[i] 129 | needsParity := (((b >> 7) ^ (b >> 6) ^ (b >> 5) ^ (b >> 4) ^ (b >> 3) ^ (b >> 2) ^ (b >> 1)) & 0x01) == 0 130 | if needsParity { 131 | bytes[i] = bytes[i] | byte(0x01) 132 | } else { 133 | bytes[i] = bytes[i] & byte(0xfe) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /ntlm/message_authenticate_test.go: -------------------------------------------------------------------------------- 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/base64" 8 | "encoding/hex" 9 | "testing" 10 | ) 11 | 12 | func checkPayloadStruct(t *testing.T, payloadStruct *PayloadStruct, len uint16, offset uint32) { 13 | if payloadStruct.Len != len || payloadStruct.Offset != offset { 14 | t.Errorf("Failed to parse payload struct %d, %d", payloadStruct.Len, payloadStruct.Offset) 15 | } 16 | } 17 | 18 | func TestParseNTLMv1AsV2(t *testing.T) { 19 | ntlmv1data := "TlRMTVNTUAADAAAAGAAYALYAAAAYABgAzgAAADQANABIAAAAIAAgAHwAAAAaABoAnAAAABAAEADmAAAAVYKQQgUCzg4AAAAPYQByAHIAYQB5ADEAMgAuAG0AcwBnAHQAcwB0AC4AcgBlAHUAdABlAHIAcwAuAGMAbwBtAHUAcwBlAHIAcwB0AHIAZQBzAHMAMQAwADAAMAAwADgATgBZAEMAVgBBADEAMgBTADIAQwBNAFMAQQDguXWdC2hLH+C5dZ0LaEsf4Ll1nQtoSx9nI+fkE73qtElnkDiSQbxfcDN9zbtO1qfyK3ZTI6CUhvjxmXnpZEjY" 20 | authBytes, err := base64.StdEncoding.DecodeString(ntlmv1data) 21 | _, err = ParseAuthenticateMessage(authBytes, 2) 22 | if err == nil { 23 | t.Error("Should have returned error when tring to parse an NTLMv1 authenticate message as NTLMv2") 24 | } 25 | _, err = ParseAuthenticateMessage(authBytes, 1) 26 | if err != nil { 27 | t.Error("Should not have returned error when tring to parse an NTLMv1 authenticate message") 28 | } 29 | } 30 | 31 | func TestAuthenticateNtlmV1(t *testing.T) { 32 | authenticateMessage := "TlRMTVNTUAADAAAAGAAYAIgAAAAYABgAoAAAAAAAAABYAAAAIAAgAFgAAAAQABAAeAAAABAAEAC4AAAAVYKQYgYBsR0AAAAP2BgW++b14Dh6Z5B4Xs1DiHAAYQB1AGwAQABwAGEAdQBsAGQAaQB4AC4AbgBlAHQAVwBJAE4ANwBfAEkARQA4ACugxZFzvHB4P6LdKbbZpiYHo2ErZURLiSugxZFzvHB4P6LdKbbZpiYHo2ErZURLibmpCUlnbq2I4LAdEhLdg7I=" 33 | authenticateData, err := base64.StdEncoding.DecodeString(authenticateMessage) 34 | 35 | if err != nil { 36 | t.Error("Could not base64 decode message data") 37 | } 38 | 39 | a, err := ParseAuthenticateMessage(authenticateData, 1) 40 | if err != nil { 41 | t.Error("Could not parse authenticate message") 42 | } 43 | 44 | a.String() 45 | 46 | outBytes := a.Bytes() 47 | 48 | if len(outBytes) > 0 { 49 | reparsed, err := ParseAuthenticateMessage(outBytes, 1) 50 | if err != nil { 51 | t.Error("Could not re-parse authenticate message") 52 | } 53 | if reparsed.String() != a.String() { 54 | t.Error("Reparsed message is not the same") 55 | } 56 | } else { 57 | t.Error("Invalid authenticate messsage bytes") 58 | } 59 | } 60 | 61 | func TestAuthenticateNtlmV2(t *testing.T) { 62 | authenticateMessage := "TlRMTVNTUAADAAAAGAAYAI4AAAAGAQYBpgAAAAAAAABYAAAAIAAgAFgAAAAWABYAeAAAABAAEACsAQAAVYKQQgYAchcAAAAPpdhi9ItaLWwSGpFMT4VQbnAAYQB1AGwAQABwAGEAdQBsAGQAaQB4AC4AbgBlAHQASQBQAC0AMABBADAAQwAzAEEAMQBFAAE/QEbbIB1InAX5KMgp4s4wmpPZ9jp9T3EC95rRY01DhMSv1kei5wYBAQAAAAAAADM6xfahoM0BMJqT2fY6fU8AAAAAAgAOAFIARQBVAFQARQBSAFMAAQAcAFUASwBCAFAALQBDAEIAVABSAE0ARgBFADAANgAEABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAMANAB1AGsAYgBwAC0AYwBiAHQAcgBtAGYAZQAwADYALgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQABQAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAAIADAAMAAAAAAAAAAAAAAAADAAAFaspfI82pMCKSuN2L09orn37EQVvxCSqVqQhCloFhQeAAAAAAAAAADRgm1iKYwwmIF3axms/dIe" 63 | authenticateData, err := base64.StdEncoding.DecodeString(authenticateMessage) 64 | 65 | if err != nil { 66 | t.Error("Could not base64 decode message data") 67 | } 68 | 69 | a, err := ParseAuthenticateMessage(authenticateData, 2) 70 | 71 | if err != nil || a == nil { 72 | t.Error("Failed to parse authenticate message " + err.Error()) 73 | } 74 | 75 | checkPayloadStruct(t, a.LmChallengeResponse, 24, 142) 76 | checkPayloadStruct(t, a.NtChallengeResponseFields, 262, 166) 77 | checkPayloadStruct(t, a.DomainName, 0, 88) 78 | checkPayloadStruct(t, a.UserName, 32, 88) 79 | checkPayloadStruct(t, a.Workstation, 22, 120) 80 | checkPayloadStruct(t, a.EncryptedRandomSessionKey, 16, 428) 81 | 82 | if a.NegotiateFlags != uint32(1116766805) { 83 | t.Errorf("Authenticate negotiate flags not correct should be %d got %d", uint32(1116766805), a.NegotiateFlags) 84 | } 85 | 86 | mic, err := hex.DecodeString("a5d862f48b5a2d6c121a914c4f85506e") 87 | if !bytes.Equal(a.Mic, mic) { 88 | t.Errorf("Mic not correct, should be %s, got %s", "a5d862f48b5a2d6c121a914c4f85506e", hex.EncodeToString(a.Mic)) 89 | } 90 | 91 | if len(a.Payload) != 356 { 92 | t.Errorf("Length of payload is incorrect got: %d, should be %d", len(a.Payload), 356) 93 | } 94 | 95 | a.String() 96 | 97 | // Generate the bytes from the message and reparse it and make sure that works 98 | bytes := a.Bytes() 99 | if len(bytes) == 0 { 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ntlm/signature.go: -------------------------------------------------------------------------------- 1 | //Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information 2 | 3 | package ntlm 4 | 5 | import ( 6 | rc4P "crypto/rc4" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "fmt" 10 | ) 11 | 12 | type NtlmsspMessageSignature struct { 13 | ByteData []byte 14 | // A 32-bit unsigned integer that contains the signature version. This field MUST be 0x00000001. 15 | Version []byte 16 | // A 4-byte array that contains the random pad for the message. 17 | RandomPad []byte 18 | // A 4-byte array that contains the checksum for the message. 19 | CheckSum []byte 20 | // A 32-bit unsigned integer that contains the NTLM sequence number for this application message. 21 | SeqNum []byte 22 | } 23 | 24 | func (n *NtlmsspMessageSignature) String() string { 25 | return fmt.Sprintf("NtlmsspMessageSignature: %s", hex.EncodeToString(n.Bytes())) 26 | } 27 | 28 | func (n *NtlmsspMessageSignature) Bytes() []byte { 29 | if n.ByteData != nil { 30 | return n.ByteData 31 | } else { 32 | return concat(n.Version, n.RandomPad, n.CheckSum, n.SeqNum) 33 | } 34 | return nil 35 | } 36 | 37 | // Define SEAL(Handle, SigningKey, SeqNum, Message) as 38 | func seal(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, message []byte) (sealedMessage []byte, sig *NtlmsspMessageSignature) { 39 | sealedMessage = rc4(handle, message) 40 | sig = mac(negFlags, handle, signingKey, uint32(seqNum), message) 41 | return 42 | } 43 | 44 | // Define SIGN(Handle, SigningKey, SeqNum, Message) as 45 | func sign(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, message []byte) []byte { 46 | return concat(message, mac(negFlags, handle, signingKey, uint32(seqNum), message).Bytes()) 47 | } 48 | 49 | func mac(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, message []byte) (result *NtlmsspMessageSignature) { 50 | if NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(negFlags) { 51 | result = macWithExtendedSessionSecurity(negFlags, handle, signingKey, seqNum, message) 52 | } else { 53 | result = macWithoutExtendedSessionSecurity(handle, seqNum, message) 54 | } 55 | return result 56 | } 57 | 58 | // Define MAC(Handle, SigningKey, SeqNum, Message) as 59 | // Set NTLMSSP_MESSAGE_SIGNATURE.Version to 0x00000001 60 | // Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to CRC32(Message) 61 | // Set NTLMSSP_MESSAGE_SIGNATURE.RandomPad RC4(Handle, RandomPad) 62 | // Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to RC4(Handle, NTLMSSP_MESSAGE_SIGNATURE.Checksum) 63 | // Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to RC4(Handle, 0x00000000) 64 | // If (connection oriented) 65 | // Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to NTLMSSP_MESSAGE_SIGNATURE.SeqNum XOR SeqNum 66 | // Set SeqNum to SeqNum + 1 67 | // Else 68 | // Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to NTLMSSP_MESSAGE_SIGNATURE.SeqNum XOR (application supplied SeqNum) 69 | // EndIf 70 | // Set NTLMSSP_MESSAGE_SIGNATURE.RandomPad to 0 71 | // End 72 | func macWithoutExtendedSessionSecurity(handle *rc4P.Cipher, seqNum uint32, message []byte) *NtlmsspMessageSignature { 73 | sig := new(NtlmsspMessageSignature) 74 | 75 | seqNumBytes := make([]byte, 4) 76 | binary.LittleEndian.PutUint32(seqNumBytes, seqNum) 77 | 78 | sig.Version = []byte{0x01, 0x00, 0x00, 0x00} 79 | sig.CheckSum = make([]byte, 4) 80 | binary.LittleEndian.PutUint32(sig.CheckSum, crc32(message)) 81 | sig.RandomPad = rc4(handle, zeroBytes(4)) 82 | sig.CheckSum = rc4(handle, sig.CheckSum) 83 | sig.SeqNum = rc4(handle, zeroBytes(4)) 84 | for i := 0; i < 4; i++ { 85 | sig.SeqNum[i] = sig.SeqNum[i] ^ seqNumBytes[i] 86 | } 87 | sig.RandomPad = zeroBytes(4) 88 | return sig 89 | } 90 | 91 | // Define MAC(Handle, SigningKey, SeqNum, Message) as 92 | // Set NTLMSSP_MESSAGE_SIGNATURE.Version to 0x00000001 93 | // if Key Exchange Key Negotiated 94 | // Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to RC4(Handle, HMAC_MD5(SigningKey, ConcatenationOf(SeqNum, Message))[0..7]) 95 | // else 96 | // Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to HMAC_MD5(SigningKey, ConcatenationOf(SeqNum, Message))[0..7] 97 | // end 98 | // Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to SeqNum 99 | // Set SeqNum to SeqNum + 1 100 | // EndDefine 101 | func macWithExtendedSessionSecurity(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, message []byte) *NtlmsspMessageSignature { 102 | sig := new(NtlmsspMessageSignature) 103 | sig.Version = []byte{0x01, 0x00, 0x00, 0x00} 104 | seqNumBytes := make([]byte, 4) 105 | binary.LittleEndian.PutUint32(seqNumBytes, seqNum) 106 | sig.CheckSum = hmacMd5(signingKey, concat(seqNumBytes, message))[0:8] 107 | if NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(negFlags) { 108 | sig.CheckSum = rc4(handle, sig.CheckSum) 109 | } 110 | sig.SeqNum = seqNumBytes 111 | return sig 112 | } 113 | 114 | func reinitSealingKey(key []byte, sequenceNumber int) (handle *rc4P.Cipher, err error) { 115 | seqNumBytes := make([]byte, 4) 116 | binary.LittleEndian.PutUint32(seqNumBytes, uint32(sequenceNumber)) 117 | newKey := md5(concat(key, seqNumBytes)) 118 | handle, err = rc4Init(newKey) 119 | return handle, err 120 | } 121 | -------------------------------------------------------------------------------- /utils/test_auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | 7 | "github.com/ThomsonReutersEikon/go-ntlm/ntlm" 8 | ) 9 | 10 | func main() { 11 | // ntlm v2 12 | // challengeMessage := "TlRMTVNTUAACAAAAAAAAADgAAABVgphiPXSy0E6+HrMAAAAAAAAAAKIAogA4AAAABQEoCgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA=" 13 | // authenticateMessage := "TlRMTVNTUAADAAAAGAAYALYAAADSANIAzgAAADQANABIAAAAIAAgAHwAAAAaABoAnAAAABAAEACgAQAAVYKQQgUCzg4AAAAPYQByAHIAYQB5ADEAMgAuAG0AcwBnAHQAcwB0AC4AcgBlAHUAdABlAHIAcwAuAGMAbwBtAHUAcwBlAHIAcwB0AHIAZQBzAHMAMQAwADAAMAAwADgATgBZAEMAVgBBADEAMgBTADIAQwBNAFMAQQBPYrLjU4h0YlWZeEoNvTJtBQMnnJuAeUwsP+vGmAHNRBpgZ+4ChQLqAQEAAAAAAACPFEIFjx7OAQUDJ5ybgHlMAAAAAAIADgBSAEUAVQBUAEUAUgBTAAEAHABVAEsAQgBQAC0AQwBCAFQAUgBNAEYARQAwADYABAAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAADADQAdQBrAGIAcAAtAGMAYgB0AHIAbQBmAGUAMAA2AC4AUgBlAHUAdABlAHIAcwAuAG4AZQB0AAUAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAAAAAAAAAAANuvnqD3K88ZpjkLleL0NW" 14 | 15 | //LCS v1 16 | //challengeMessage := "TlRMTVNTUAACAAAAAAAAADgAAADzgpjid08w9p89DLUAAAAAAAAAAPAA8AA4AAAABQLODgAAAA8CAA4AQQBSAFIAQQBZADEAMgABABYATgBZAEMAUwBNAFMARwA5ADkAMQAyAAQANABhAHIAcgBhAHkAMQAyAC4AbQBzAGcAdABzAHQALgByAGUAdQB0AGUAcgBzAC4AYwBvAG0AAwBMAE4AWQBDAFMATQBTAEcAOQA5ADEAMgAuAGEAcgByAGEAeQAxADIALgBtAHMAZwB0AHMAdAAuAHIAZQB1AHQAZQByAHMALgBjAG8AbQAFADQAYQByAHIAYQB5ADEAMgAuAG0AcwBnAHQAcwB0AC4AcgBlAHUAdABlAHIAcwAuAGMAbwBtAAAAAAA=" 17 | //authenticateMessage := "TlRMTVNTUAADAAAAGAAYAKwAAAAYABgAxAAAAAAAAABYAAAANgA2AFgAAAAeAB4AjgAAABAAEADcAAAAVYKQYgYBsR0AAAAPUJSCwwcYcGpE0Zp9GsD3RDAANQAwADAANAA1AC4AcgBtAHcAYQB0AGUAcwB0AEAAcgBlAHUAdABlAHIAcwAuAGMAbwBtAFcASQBOAC0AMABEAEQAQQBCAEsAQwAxAFUASQA4ALIsDLYZktr3YlJDLyVT6GHgwNA+DFdM87IsDLYZktr3YlJDLyVT6GHgwNA+DFdM851g+vaa4CHvomwyYmjbB1M=" 18 | 19 | //US 20 | //challengeMessage := "TlRMTVNTUAACAAAAAAAAADgAAABVgphisF5WgZrWn4MAAAAAAAAAAKIAogA4AAAABQEoCgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA=" 21 | //authenticateMessage := "TlRMTVNTUAADAAAAGAAYAKwAAAAYABgAxAAAAAAAAABYAAAANgA2AFgAAAAeAB4AjgAAABAAEADcAAAAVYKQYgYBsR0AAAAPJc+NGJ4qgACnkkGb9J8RezAANQAwADAANAA1AC4AcgBtAHcAYQB0AGUAcwB0AEAAcgBlAHUAdABlAHIAcwAuAGMAbwBtAFcASQBOAC0AMABEAEQAQQBCAEsAQwAxAFUASQA4AJLPhCq8UHZjb5sEjtoaJtWBY2ZwNZyujpLPhCq8UHZjb5sEjtoaJtWBY2ZwNZyujtW8TsZdZ6PMc1ipWbL7VgY=" 22 | 23 | //US again 24 | challengeMessage := "TlRMTVNTUAACAAAAAAAAADgAAABVgphiMx43owKH33MAAAAAAAAAAKIAogA4AAAABQEoCgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA=" 25 | authenticateMessage := "TlRMTVNTUAADAAAAGAAYAKwAAAAYABgAxAAAAAAAAABYAAAANgA2AFgAAAAeAB4AjgAAABAAEADcAAAAVYKQYgYBsR0AAAAPukU9WmBJLdSLU2NvXjNgUzAANQAwADAANAA1AC4AcgBtAHcAYQB0AGUAcwB0AEAAcgBlAHUAdABlAHIAcwAuAGMAbwBtAFcASQBOAC0AMABEAEQAQQBCAEsAQwAxAFUASQA4AOLIAEYvI6zgw2+MBf8xHSTZhIfVaKIIFuLIAEYvI6zgw2+MBf8xHSTZhIfVaKIIFroZDwl770tY/oFQk38nnuI=" 26 | 27 | server, err := ntlm.CreateServerSession(ntlm.Version2, ntlm.ConnectionlessMode) 28 | server.SetUserInfo("050045.rmwatest@reuters.com", "Welcome1", "") 29 | 30 | challengeData, _ := base64.StdEncoding.DecodeString(challengeMessage) 31 | c, _ := ntlm.ParseChallengeMessage(challengeData) 32 | 33 | fmt.Println("----- Challenge Message ----- ") 34 | fmt.Println(c.String()) 35 | fmt.Println("----- END Challenge Message ----- ") 36 | 37 | authenticateData, _ := base64.StdEncoding.DecodeString(authenticateMessage) 38 | var context ntlm.ServerSession 39 | 40 | msg, err := ntlm.ParseAuthenticateMessage(authenticateData, 2) 41 | if err != nil { 42 | msg2, newErr := ntlm.ParseAuthenticateMessage(authenticateData, 1) 43 | if newErr != nil { 44 | fmt.Printf("Error ParseAuthenticateMessage , %s", err) 45 | return 46 | } 47 | 48 | // Message parsed correctly as NTLMv1 so assume the session is v1 and reset the server session 49 | newContext, err := ntlm.CreateServerSession(ntlm.Version1, ntlm.ConnectionlessMode) 50 | newContext.SetUserInfo(server.GetUserInfo()) 51 | if err != nil { 52 | fmt.Println("Could not create NTLMv1 session") 53 | return 54 | } 55 | 56 | // Need the originally generated server challenge so we can process the response 57 | newContext.SetServerChallenge(c.ServerChallenge) 58 | // err = server.ProcessAuthenticateMessage(msg) 59 | err = newContext.ProcessAuthenticateMessage(msg2) 60 | if err != nil { 61 | fmt.Printf("Could not process authenticate v1 message: %s\n", err) 62 | return 63 | } 64 | // Set the security context to now be NTLMv1 65 | context = newContext 66 | fmt.Println("----- Authenticate Message ----- ") 67 | fmt.Println(msg2.String()) 68 | fmt.Println("----- END Authenticate Message ----- ") 69 | 70 | } else { 71 | context = server 72 | // Need the server challenge to be set 73 | server.SetServerChallenge(c.ServerChallenge) 74 | 75 | // err = server.ProcessAuthenticateMessage(msg) 76 | err = context.ProcessAuthenticateMessage(msg) 77 | if err != nil { 78 | fmt.Printf("Could not process authenticate message: %s\n", err) 79 | return 80 | } 81 | fmt.Println("----- Authenticate Message ----- ") 82 | fmt.Println(msg.String()) 83 | fmt.Println("----- END Authenticate Message ----- ") 84 | 85 | } 86 | 87 | fmt.Println("success") 88 | } 89 | -------------------------------------------------------------------------------- /ntlm/challenge_responses.go: -------------------------------------------------------------------------------- 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/hex" 8 | "errors" 9 | "fmt" 10 | ) 11 | 12 | // NTLMv1 13 | // ****** 14 | type NtlmV1Response struct { 15 | // 24 byte array 16 | Response []byte 17 | } 18 | 19 | func (n *NtlmV1Response) String() string { 20 | return fmt.Sprintf("NtlmV1Response: %s", hex.EncodeToString(n.Response)) 21 | } 22 | 23 | func ReadNtlmV1Response(bytes []byte) (*NtlmV1Response, error) { 24 | r := new(NtlmV1Response) 25 | r.Response = bytes[0:24] 26 | return r, nil 27 | } 28 | 29 | // *** NTLMv2 30 | // The NTLMv2_CLIENT_CHALLENGE structure defines the client challenge in the AUTHENTICATE_MESSAGE. 31 | // This structure is used only when NTLM v2 authentication is configured. 32 | type NtlmV2ClientChallenge struct { 33 | // An 8-bit unsigned char that contains the current version of the challenge response type. 34 | // This field MUST be 0x01. 35 | RespType byte 36 | // An 8-bit unsigned char that contains the maximum supported version of the challenge response type. 37 | // This field MUST be 0x01. 38 | HiRespType byte 39 | // A 16-bit unsigned integer that SHOULD be 0x0000 and MUST be ignored on receipt. 40 | Reserved1 uint16 41 | // A 32-bit unsigned integer that SHOULD be 0x00000000 and MUST be ignored on receipt. 42 | Reserved2 uint32 43 | // A 64-bit unsigned integer that contains the current system time, represented as the number of 100 nanosecond 44 | // ticks elapsed since midnight of January 1, 1601 (UTC). 45 | TimeStamp []byte 46 | // An 8-byte array of unsigned char that contains the client's ClientChallenge (section 3.1.5.1.2). 47 | ChallengeFromClient []byte 48 | // A 32-bit unsigned integer that SHOULD be 0x00000000 and MUST be ignored on receipt. 49 | Reserved3 uint32 50 | AvPairs *AvPairs 51 | } 52 | 53 | func (n *NtlmV2ClientChallenge) String() string { 54 | var buffer bytes.Buffer 55 | 56 | buffer.WriteString("NTLM v2 ClientChallenge\n") 57 | buffer.WriteString(fmt.Sprintf("Timestamp: %s\n", hex.EncodeToString(n.TimeStamp))) 58 | buffer.WriteString(fmt.Sprintf("ChallengeFromClient: %s\n", hex.EncodeToString(n.ChallengeFromClient))) 59 | buffer.WriteString("AvPairs\n") 60 | buffer.WriteString(n.AvPairs.String()) 61 | 62 | return buffer.String() 63 | } 64 | 65 | // The NTLMv2_RESPONSE structure defines the NTLMv2 authentication NtChallengeResponse in the AUTHENTICATE_MESSAGE. 66 | // This response is used only when NTLMv2 authentication is configured. 67 | type NtlmV2Response struct { 68 | // A 16-byte array of unsigned char that contains the client's NT challenge- response as defined in section 3.3.2. 69 | // Response corresponds to the NTProofStr variable from section 3.3.2. 70 | Response []byte 71 | // A variable-length byte array that contains the ClientChallenge as defined in section 3.3.2. 72 | // ChallengeFromClient corresponds to the temp variable from section 3.3.2. 73 | NtlmV2ClientChallenge *NtlmV2ClientChallenge 74 | } 75 | 76 | func (n *NtlmV2Response) String() string { 77 | var buffer bytes.Buffer 78 | 79 | buffer.WriteString("NTLM v2 Response\n") 80 | buffer.WriteString(fmt.Sprintf("Response: %s\n", hex.EncodeToString(n.Response))) 81 | buffer.WriteString(n.NtlmV2ClientChallenge.String()) 82 | 83 | return buffer.String() 84 | } 85 | 86 | func ReadNtlmV2Response(bytes []byte) (*NtlmV2Response, error) { 87 | r := new(NtlmV2Response) 88 | r.Response = bytes[0:16] 89 | r.NtlmV2ClientChallenge = new(NtlmV2ClientChallenge) 90 | c := r.NtlmV2ClientChallenge 91 | c.RespType = bytes[16] 92 | c.HiRespType = bytes[17] 93 | 94 | if c.RespType != 1 || c.HiRespType != 1 { 95 | return nil, errors.New("Does not contain a valid NTLM v2 client challenge - could be NTLMv1.") 96 | } 97 | 98 | // Ignoring - 2 bytes reserved 99 | // c.Reserved1 100 | // Ignoring - 4 bytes reserved 101 | // c.Reserved2 102 | c.TimeStamp = bytes[24:32] 103 | c.ChallengeFromClient = bytes[32:40] 104 | // Ignoring - 4 bytes reserved 105 | // c.Reserved3 106 | c.AvPairs = ReadAvPairs(bytes[44:]) 107 | return r, nil 108 | } 109 | 110 | // LMv1 111 | // **** 112 | type LmV1Response struct { 113 | // 24 bytes 114 | Response []byte 115 | } 116 | 117 | func ReadLmV1Response(bytes []byte) *LmV1Response { 118 | r := new(LmV1Response) 119 | r.Response = bytes[0:24] 120 | return r 121 | } 122 | 123 | func (l *LmV1Response) String() string { 124 | return fmt.Sprintf("LmV1Response: %s", hex.EncodeToString(l.Response)) 125 | } 126 | 127 | // *** LMv2 128 | type LmV2Response struct { 129 | // A 16-byte array of unsigned char that contains the client's LM challenge-response. 130 | // This is the portion of the LmChallengeResponse field to which the HMAC_MD5 algorithm 131 | /// has been applied, as defined in section 3.3.2. Specifically, Response corresponds 132 | // to the result of applying the HMAC_MD5 algorithm, using the key ResponseKeyLM, to a 133 | // message consisting of the concatenation of the ResponseKeyLM, ServerChallenge and ClientChallenge. 134 | Response []byte 135 | // An 8-byte array of unsigned char that contains the client's ClientChallenge, as defined in section 3.1.5.1.2. 136 | ChallengeFromClient []byte 137 | } 138 | 139 | func ReadLmV2Response(bytes []byte) *LmV2Response { 140 | r := new(LmV2Response) 141 | r.Response = bytes[0:16] 142 | r.ChallengeFromClient = bytes[16:24] 143 | return r 144 | } 145 | 146 | func (l *LmV2Response) String() string { 147 | var buffer bytes.Buffer 148 | 149 | buffer.WriteString("LM v2 Response\n") 150 | buffer.WriteString(fmt.Sprintf("Response: %s\n", hex.EncodeToString(l.Response))) 151 | buffer.WriteString(fmt.Sprintf("ChallengeFromClient: %s\n", hex.EncodeToString(l.ChallengeFromClient))) 152 | 153 | return buffer.String() 154 | } 155 | -------------------------------------------------------------------------------- /ntlm/message_challenge.go: -------------------------------------------------------------------------------- 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 | challenge.Signature = body[0:8] 60 | if !bytes.Equal(challenge.Signature, []byte("NTLMSSP\x00")) { 61 | return challenge, errors.New("Invalid NTLM message signature") 62 | } 63 | 64 | challenge.MessageType = binary.LittleEndian.Uint32(body[8:12]) 65 | if challenge.MessageType != 2 { 66 | return challenge, errors.New("Invalid NTLM message type should be 0x00000002 for challenge message") 67 | } 68 | 69 | var err error 70 | 71 | challenge.TargetName, err = ReadStringPayload(12, body) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | challenge.NegotiateFlags = binary.LittleEndian.Uint32(body[20:24]) 77 | 78 | challenge.ServerChallenge = body[24:32] 79 | 80 | challenge.Reserved = body[32:40] 81 | 82 | challenge.TargetInfoPayloadStruct, err = ReadBytePayload(40, body) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | challenge.TargetInfo = ReadAvPairs(challenge.TargetInfoPayloadStruct.Payload) 88 | 89 | offset := 48 90 | 91 | if NTLMSSP_NEGOTIATE_VERSION.IsSet(challenge.NegotiateFlags) { 92 | challenge.Version, err = ReadVersionStruct(body[offset : offset+8]) 93 | if err != nil { 94 | return nil, err 95 | } 96 | offset = offset + 8 97 | } 98 | 99 | challenge.Payload = body[offset:] 100 | 101 | return challenge, nil 102 | } 103 | 104 | func (c *ChallengeMessage) Bytes() []byte { 105 | payloadLen := int(c.TargetName.Len + c.TargetInfoPayloadStruct.Len) 106 | messageLen := 8 + 4 + 8 + 4 + 8 + 8 + 8 + 8 107 | payloadOffset := uint32(messageLen) 108 | 109 | messageBytes := make([]byte, 0, messageLen+payloadLen) 110 | buffer := bytes.NewBuffer(messageBytes) 111 | 112 | buffer.Write(c.Signature) 113 | binary.Write(buffer, binary.LittleEndian, c.MessageType) 114 | 115 | c.TargetName.Offset = payloadOffset 116 | buffer.Write(c.TargetName.Bytes()) 117 | payloadOffset += uint32(c.TargetName.Len) 118 | 119 | binary.Write(buffer, binary.LittleEndian, c.NegotiateFlags) 120 | buffer.Write(c.ServerChallenge) 121 | buffer.Write(make([]byte, 8)) 122 | 123 | c.TargetInfoPayloadStruct.Offset = payloadOffset 124 | buffer.Write(c.TargetInfoPayloadStruct.Bytes()) 125 | payloadOffset += uint32(c.TargetInfoPayloadStruct.Len) 126 | 127 | // if(c.Version != nil) { 128 | buffer.Write(c.Version.Bytes()) 129 | // } else { 130 | // buffer.Write(make([]byte, 8)) 131 | //} 132 | 133 | // Write out the payloads 134 | buffer.Write(c.TargetName.Payload) 135 | buffer.Write(c.TargetInfoPayloadStruct.Payload) 136 | 137 | return buffer.Bytes() 138 | } 139 | 140 | func (c *ChallengeMessage) getLowestPayloadOffset() int { 141 | payloadStructs := [...]*PayloadStruct{c.TargetName, c.TargetInfoPayloadStruct} 142 | 143 | // Find the lowest offset value 144 | lowest := 9999 145 | for i := range payloadStructs { 146 | p := payloadStructs[i] 147 | if p != nil && p.Offset > 0 && int(p.Offset) < lowest { 148 | lowest = int(p.Offset) 149 | } 150 | } 151 | 152 | return lowest 153 | } 154 | 155 | func (c *ChallengeMessage) String() string { 156 | var buffer bytes.Buffer 157 | 158 | buffer.WriteString("Challenge NTLM Message") 159 | buffer.WriteString(fmt.Sprintf("\nPayload Offset: %d Length: %d", c.getLowestPayloadOffset(), len(c.Payload))) 160 | buffer.WriteString(fmt.Sprintf("\nTargetName: %s", c.TargetName.String())) 161 | buffer.WriteString(fmt.Sprintf("\nServerChallenge: %s", hex.EncodeToString(c.ServerChallenge))) 162 | if c.Version != nil { 163 | buffer.WriteString(fmt.Sprintf("\nVersion: %s\n", c.Version.String())) 164 | } 165 | buffer.WriteString("\nTargetInfo") 166 | buffer.WriteString(c.TargetInfo.String()) 167 | buffer.WriteString(fmt.Sprintf("\nFlags %d\n", c.NegotiateFlags)) 168 | buffer.WriteString(FlagsToString(c.NegotiateFlags)) 169 | 170 | return buffer.String() 171 | } 172 | -------------------------------------------------------------------------------- /ntlm/av_pairs.go: -------------------------------------------------------------------------------- 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 | "fmt" 10 | ) 11 | 12 | type AvPairType uint16 13 | 14 | // MS-NLMP - 2.2.2.1 AV_PAIR 15 | const ( 16 | // Indicates that this is the last AV_PAIR in the list. AvLen MUST be 0. This type of information MUST be present in the AV pair list. 17 | MsvAvEOL AvPairType = iota 18 | // The server's NetBIOS computer name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list. 19 | MsvAvNbComputerName 20 | // The server's NetBIOS domain name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list. 21 | MsvAvNbDomainName 22 | // The fully qualified domain name (FQDN (1)) of the computer. The name MUST be in Unicode, and is not null-terminated. 23 | MsvAvDnsComputerName 24 | // The FQDN (2) of the domain. The name MUST be in Unicode, and is not null-terminate. 25 | MsvAvDnsDomainName 26 | // The FQDN (2) of the forest. The name MUST be in Unicode, and is not null-terminated.<11> 27 | MsvAvDnsTreeName 28 | // A 32-bit value indicating server or client configuration. 29 | // 0x00000001: indicates to the client that the account authentication is constrained. 30 | // 0x00000002: indicates that the client is providing message integrity in the MIC field (section 2.2.1.3) in the AUTHENTICATE_MESSAGE.<12> 31 | // 0x00000004: indicates that the client is providing a target SPN generated from an untrusted source.<13> 32 | MsvAvFlags 33 | // A FILETIME structure ([MS-DTYP] section 2.3.1) in little-endian byte order that contains the server local time.<14> 34 | MsvAvTimestamp 35 | //A Restriction_Encoding (section 2.2.2.2) structure. The Value field contains a structure representing the integrity level of the security principal, as well as a MachineID created at computer startup to identify the calling machine.<15> 36 | MsAvRestrictions 37 | // The SPN of the target server. The name MUST be in Unicode and is not null-terminated.<16> 38 | MsvAvTargetName 39 | // annel bindings hash. The Value field contains an MD5 hash ([RFC4121] section 4.1.1.2) of a gss_channel_bindings_struct ([RFC2744] section 3.11). 40 | // An all-zero value of the hash is used to indicate absence of channel bindings.<17> 41 | MsvChannelBindings 42 | ) 43 | 44 | // Helper struct that contains a list of AvPairs with helper methods for running through them 45 | type AvPairs struct { 46 | List []AvPair 47 | } 48 | 49 | func (p *AvPairs) AddAvPair(avId AvPairType, bytes []byte) { 50 | a := &AvPair{AvId: avId, AvLen: uint16(len(bytes)), Value: bytes} 51 | p.List = append(p.List, *a) 52 | } 53 | 54 | func ReadAvPairs(data []byte) *AvPairs { 55 | pairs := new(AvPairs) 56 | 57 | // Get the number of AvPairs and allocate enough AvPair structures to hold them 58 | offset := 0 59 | for i := 0; len(data) > 0 && i < 11; i++ { 60 | pair := ReadAvPair(data, offset) 61 | offset = offset + 4 + int(pair.AvLen) 62 | pairs.List = append(pairs.List, *pair) 63 | if pair.AvId == MsvAvEOL { 64 | break 65 | } 66 | } 67 | 68 | return pairs 69 | } 70 | 71 | func (p *AvPairs) Bytes() (result []byte) { 72 | totalLength := 0 73 | for i := range p.List { 74 | a := p.List[i] 75 | totalLength = totalLength + int(a.AvLen) + 4 76 | } 77 | 78 | result = make([]byte, 0, totalLength) 79 | for i := range p.List { 80 | a := p.List[i] 81 | result = append(result, a.Bytes()...) 82 | } 83 | 84 | return result 85 | } 86 | 87 | func (p *AvPairs) String() string { 88 | var buffer bytes.Buffer 89 | 90 | buffer.WriteString(fmt.Sprintf("Av Pairs (Total %d pairs)\n", len(p.List))) 91 | 92 | for i := range p.List { 93 | buffer.WriteString(p.List[i].String()) 94 | buffer.WriteString("\n") 95 | } 96 | 97 | return buffer.String() 98 | } 99 | 100 | func (p *AvPairs) Find(avType AvPairType) (result *AvPair) { 101 | for i := range p.List { 102 | pair := p.List[i] 103 | if avType == pair.AvId { 104 | result = &pair 105 | break 106 | } 107 | } 108 | return 109 | } 110 | 111 | func (p *AvPairs) ByteValue(avType AvPairType) (result []byte) { 112 | pair := p.Find(avType) 113 | if pair != nil { 114 | result = pair.Value 115 | } 116 | return 117 | } 118 | 119 | func (p *AvPairs) StringValue(avType AvPairType) (result string) { 120 | pair := p.Find(avType) 121 | if pair != nil { 122 | result = pair.UnicodeStringValue() 123 | } 124 | return 125 | } 126 | 127 | // AvPair as described by MS-NLMP 128 | type AvPair struct { 129 | AvId AvPairType 130 | AvLen uint16 131 | Value []byte 132 | } 133 | 134 | func ReadAvPair(data []byte, offset int) *AvPair { 135 | pair := new(AvPair) 136 | pair.AvId = AvPairType(binary.LittleEndian.Uint16(data[offset : offset+2])) 137 | pair.AvLen = binary.LittleEndian.Uint16(data[offset+2 : offset+4]) 138 | pair.Value = data[offset+4 : offset+4+int(pair.AvLen)] 139 | return pair 140 | } 141 | 142 | func (a *AvPair) UnicodeStringValue() string { 143 | return utf16ToString(a.Value) 144 | } 145 | 146 | func (a *AvPair) Bytes() (result []byte) { 147 | result = make([]byte, 4, a.AvLen+4) 148 | result[0] = byte(a.AvId) 149 | result[1] = byte(a.AvId >> 8) 150 | result[2] = byte(a.AvLen) 151 | result[3] = byte(a.AvLen >> 8) 152 | result = append(result, a.Value...) 153 | return 154 | } 155 | 156 | func (a *AvPair) String() string { 157 | var outString string 158 | 159 | switch a.AvId { 160 | case MsvAvEOL: 161 | outString = "MsvAvEOL" 162 | case MsvAvNbComputerName: 163 | outString = "MsAvNbComputerName: " + a.UnicodeStringValue() 164 | case MsvAvNbDomainName: 165 | outString = "MsvAvNbDomainName: " + a.UnicodeStringValue() 166 | case MsvAvDnsComputerName: 167 | outString = "MsvAvDnsComputerName: " + a.UnicodeStringValue() 168 | case MsvAvDnsDomainName: 169 | outString = "MsvAvDnsDomainName: " + a.UnicodeStringValue() 170 | case MsvAvDnsTreeName: 171 | outString = "MsvAvDnsTreeName: " + a.UnicodeStringValue() 172 | case MsvAvFlags: 173 | outString = "MsvAvFlags: " + hex.EncodeToString(a.Value) 174 | case MsvAvTimestamp: 175 | outString = "MsvAvTimestamp: " + hex.EncodeToString(a.Value) 176 | case MsAvRestrictions: 177 | outString = "MsAvRestrictions: " + hex.EncodeToString(a.Value) 178 | case MsvAvTargetName: 179 | outString = "MsvAvTargetName: " + a.UnicodeStringValue() 180 | case MsvChannelBindings: 181 | outString = "MsvChannelBindings: " + hex.EncodeToString(a.Value) 182 | default: 183 | outString = fmt.Sprintf("unknown pair type: '%d'", a.AvId) 184 | } 185 | 186 | return outString 187 | } 188 | -------------------------------------------------------------------------------- /ntlm/ntlmv2_test.go: -------------------------------------------------------------------------------- 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/base64" 8 | "encoding/hex" 9 | "strings" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func checkV2Value(t *testing.T, name string, value []byte, expected string, err error) { 15 | if err != nil { 16 | t.Errorf("NTLMv2 %s received error: %s", name, err) 17 | } else { 18 | expectedBytes, _ := hex.DecodeString(expected) 19 | if !bytes.Equal(expectedBytes, value) { 20 | t.Errorf("NTLMv2 %s is not correct got %s expected %s", name, hex.EncodeToString(value), expected) 21 | } 22 | } 23 | } 24 | 25 | func TestNTOWFv2(t *testing.T) { 26 | result := ntowfv2("User", "Password", "Domain") 27 | // Sample value from 4.2.4.1.1 in MS-NLMP 28 | expected, _ := hex.DecodeString("0c868a403bfd7a93a3001ef22ef02e3f") 29 | if !bytes.Equal(result, expected) { 30 | t.Errorf("NTOWFv2 is not correct got %s expected %s", hex.EncodeToString(result), "0c868a403bfd7a93a3001ef22ef02e3f") 31 | } 32 | } 33 | 34 | func TestNTLMv2(t *testing.T) { 35 | flags := uint32(0) 36 | flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) 37 | flags = NTLMSSP_NEGOTIATE_56.Set(flags) 38 | flags = NTLMSSP_NEGOTIATE_128.Set(flags) 39 | flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags) 40 | flags = NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags) 41 | flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) 42 | flags = NTLMSSP_TARGET_TYPE_SERVER.Set(flags) 43 | flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) 44 | flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags) 45 | flags = NTLMSSP_NEGOTIATE_SEAL.Set(flags) 46 | flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags) 47 | flags = NTLM_NEGOTIATE_OEM.Set(flags) 48 | flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags) 49 | 50 | // n := new(V2Session) 51 | // n.SetUserInfo("User","Password","Domain") 52 | // n.NegotiateFlags = flags 53 | // n.responseKeyNT, _ = hex.DecodeString("0c868a403bfd7a93a3001ef22ef02e3f") 54 | // n.responseKeyLM = n.responseKeyNT 55 | // n.clientChallenge, _ = hex.DecodeString("aaaaaaaaaaaaaaaa") 56 | // n.serverChallenge, _ = hex.DecodeString("0123456789abcdef") 57 | 58 | // Encrypted Random Session key 59 | //c5 da d2 54 4f c9 79 90 94 ce 1c e9 0b c9 d0 3e 60 | 61 | // Challenge message 62 | client := new(V2ClientSession) 63 | client.SetUserInfo("User", "Password", "Domain") 64 | 65 | challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033828ae20123456789abcdef00000000000000002400240044000000060070170000000f53006500720076006500720002000c0044006f006d00610069006e0001000c0053006500720076006500720000000000") 66 | challengeMessage, err := ParseChallengeMessage(challengeMessageBytes) 67 | if err == nil { 68 | challengeMessage.String() 69 | } else { 70 | t.Errorf("Could not parse challenge message: %s", err) 71 | } 72 | 73 | err = client.ProcessChallengeMessage(challengeMessage) 74 | if err != nil { 75 | t.Errorf("Could not process challenge message: %s", err) 76 | } 77 | 78 | server := new(V2ServerSession) 79 | server.SetUserInfo("User", "Password", "Domain") 80 | server.serverChallenge = challengeMessage.ServerChallenge 81 | 82 | // Authenticate message 83 | r := strings.NewReplacer("\n", "", "\t", "", " ", "") 84 | authenticateMessageBytes, _ := hex.DecodeString(r.Replace(` 85 | 4e544c4d535350000300000018001800 86 | 6c00000054005400840000000c000c00 87 | 48000000080008005400000010001000 88 | 5c00000010001000d8000000358288e2 89 | 0501280a0000000f44006f006d006100 90 | 69006e00550073006500720043004f00 91 | 4d005000550054004500520086c35097 92 | ac9cec102554764a57cccc19aaaaaaaa 93 | aaaaaaaa68cd0ab851e51c96aabc927b 94 | ebef6a1c010100000000000000000000 95 | 00000000aaaaaaaaaaaaaaaa00000000 96 | 02000c0044006f006d00610069006e00 97 | 01000c00530065007200760065007200 98 | 0000000000000000c5dad2544fc97990 99 | 94ce1ce90bc9d03e`)) 100 | 101 | authenticateMessage, err := ParseAuthenticateMessage(authenticateMessageBytes, 2) 102 | if err == nil { 103 | authenticateMessage.String() 104 | } else { 105 | t.Errorf("Could not parse authenticate message: %s", err) 106 | } 107 | 108 | err = server.ProcessAuthenticateMessage(authenticateMessage) 109 | if err != nil { 110 | t.Errorf("Could not process authenticate message: %s", err) 111 | } 112 | 113 | checkV2Value(t, "SessionBaseKey", server.sessionBaseKey, "8de40ccadbc14a82f15cb0ad0de95ca3", nil) 114 | checkV2Value(t, "NTChallengeResponse", server.ntChallengeResponse[0:16], "68cd0ab851e51c96aabc927bebef6a1c", nil) 115 | checkV2Value(t, "LMChallengeResponse", server.lmChallengeResponse, "86c35097ac9cec102554764a57cccc19aaaaaaaaaaaaaaaa", nil) 116 | 117 | checkV2Value(t, "client seal key", server.ClientSealingKey, "59f600973cc4960a25480a7c196e4c58", nil) 118 | checkV2Value(t, "client signing key", server.ClientSigningKey, "4788dc861b4782f35d43fd98fe1a2d39", nil) 119 | 120 | // Have the server generate an initial challenge message 121 | challenge, err := server.GenerateChallengeMessage() 122 | challenge.String() 123 | 124 | // Have the client process this server challenge message 125 | client = new(V2ClientSession) 126 | client.SetUserInfo("User", "Password", "Domain") 127 | err = client.ProcessChallengeMessage(challenge) 128 | if err != nil { 129 | t.Errorf("Could not process server generated challenge message: %s", err) 130 | } 131 | // TODO: we should be able to use the ntlm library end to end to make sure 132 | // that Mac, VerifyMac 133 | 134 | // // the client should be able to verify the server's mac 135 | // sig := "" 136 | // mac, err := server.Mac([]byte(sig), 100) 137 | // if err != nil { 138 | // t.Errorf("Could not generate a mac for %s", sig) 139 | // } 140 | // matches, err := client.VerifyMac([]byte(sig), mac, 100) 141 | // if err != nil { 142 | // t.Errorf("Could not verify mac for %s (mac = %v)", sig, mac) 143 | // } 144 | // if !matches { 145 | // t.Errorf("Server's Mac couldn't be verified by client") 146 | // } 147 | 148 | // mac, err = client.Mac([]byte(sig), 100) 149 | // if err != nil { 150 | // t.Errorf("Could not generate a mac for %s", sig) 151 | // } 152 | // matches, err = server.VerifyMac([]byte(sig), mac, 100) 153 | // if err != nil { 154 | // t.Errorf("Could not verify mac for %s (mac = %v)", sig, mac) 155 | // } 156 | // if !matches { 157 | // t.Errorf("Client's Mac couldn't be verified by server") 158 | // } 159 | } 160 | 161 | func TestNTLMv2WithDomain(t *testing.T) { 162 | authenticateMessage := "TlRMTVNTUAADAAAAGAAYALYAAADSANIAzgAAADQANABIAAAAIAAgAHwAAAAaABoAnAAAABAAEACgAQAAVYKQQgUCzg4AAAAPYQByAHIAYQB5ADEAMgAuAG0AcwBnAHQAcwB0AC4AcgBlAHUAdABlAHIAcwAuAGMAbwBtAHUAcwBlAHIAcwB0AHIAZQBzAHMAMQAwADAAMAAwADgATgBZAEMAVgBBADEAMgBTADIAQwBNAFMAQQBPYrLjU4h0YlWZeEoNvTJtBQMnnJuAeUwsP+vGmAHNRBpgZ+4ChQLqAQEAAAAAAACPFEIFjx7OAQUDJ5ybgHlMAAAAAAIADgBSAEUAVQBUAEUAUgBTAAEAHABVAEsAQgBQAC0AQwBCAFQAUgBNAEYARQAwADYABAAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAADADQAdQBrAGIAcAAtAGMAYgB0AHIAbQBmAGUAMAA2AC4AUgBlAHUAdABlAHIAcwAuAG4AZQB0AAUAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAAAAAAAAAAANuvnqD3K88ZpjkLleL0NW" 163 | 164 | server := new(V2ServerSession) 165 | server.SetUserInfo("blahblah", "Welcome1", "blahblah") 166 | 167 | authenticateData, _ := base64.StdEncoding.DecodeString(authenticateMessage) 168 | a, _ := ParseAuthenticateMessage(authenticateData, 2) 169 | 170 | serverChallenge, _ := hex.DecodeString("3d74b2d04ebe1eb3") 171 | server.SetServerChallenge(serverChallenge) 172 | 173 | err := server.ProcessAuthenticateMessage(a) 174 | if err != nil { 175 | t.Error("Could not process authenticate message: %s\n", err) 176 | } 177 | } 178 | 179 | func TestWindowsTimeConversion(t *testing.T) { 180 | // From http://davenport.sourceforge.net/ntlm.html#theType3Message 181 | // Next, the blob is constructed. The timestamp is the most tedious part of this; looking at the clock on my desk, 182 | // it's about 6:00 AM EDT on June 17th, 2003. In Unix time, that would be 1055844000 seconds after the Epoch. 183 | // Adding 11644473600 will give us seconds after January 1, 1601 (12700317600). Multiplying by 107 (10000000) 184 | // will give us tenths of a microsecond (127003176000000000). As a little-endian 64-bit value, this is 185 | // "0x0090d336b734c301" (in hexadecimal). 186 | unix := time.Unix(1055844000, 0) 187 | result := timeToWindowsFileTime(unix) 188 | checkV2Value(t, "Timestamp", result, "0090d336b734c301", nil) 189 | } 190 | -------------------------------------------------------------------------------- /ntlm/message_authenticate.go: -------------------------------------------------------------------------------- 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 | am.Signature = body[0:8] 60 | if !bytes.Equal(am.Signature, []byte("NTLMSSP\x00")) { 61 | return nil, errors.New("Invalid NTLM message signature") 62 | } 63 | 64 | am.MessageType = binary.LittleEndian.Uint32(body[8:12]) 65 | if am.MessageType != 3 { 66 | return nil, errors.New("Invalid NTLM message type should be 0x00000003 for authenticate message") 67 | } 68 | 69 | var err error 70 | 71 | am.LmChallengeResponse, err = ReadBytePayload(12, body) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | if ntlmVersion == 2 { 77 | am.LmV2Response = ReadLmV2Response(am.LmChallengeResponse.Payload) 78 | } else { 79 | am.LmV1Response = ReadLmV1Response(am.LmChallengeResponse.Payload) 80 | } 81 | 82 | am.NtChallengeResponseFields, err = ReadBytePayload(20, body) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | // Check to see if this is a v1 or v2 response 88 | if ntlmVersion == 2 { 89 | am.NtlmV2Response, err = ReadNtlmV2Response(am.NtChallengeResponseFields.Payload) 90 | } else { 91 | am.NtlmV1Response, err = ReadNtlmV1Response(am.NtChallengeResponseFields.Payload) 92 | } 93 | 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | am.DomainName, err = ReadStringPayload(28, body) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | am.UserName, err = ReadStringPayload(36, body) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | am.Workstation, err = ReadStringPayload(44, body) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | lowestOffset := am.getLowestPayloadOffset() 114 | offset := 52 115 | 116 | // If the lowest payload offset is 52 then: 117 | // The Session Key, flags, and OS Version structure are omitted. The data (payload) block in this case starts after the Workstation Name 118 | // security buffer header, at offset 52. This form is seen in older Win9x-based systems. This is from the davenport notes about Type 3 119 | // messages and this information does not seem to be present in the MS-NLMP document 120 | if lowestOffset > 52 { 121 | am.EncryptedRandomSessionKey, err = ReadBytePayload(offset, body) 122 | if err != nil { 123 | return nil, err 124 | } 125 | offset = offset + 8 126 | 127 | am.NegotiateFlags = binary.LittleEndian.Uint32(body[offset : offset+4]) 128 | offset = offset + 4 129 | 130 | // 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> 131 | if NTLMSSP_NEGOTIATE_VERSION.IsSet(am.NegotiateFlags) { 132 | am.Version, err = ReadVersionStruct(body[offset : offset+8]) 133 | if err != nil { 134 | return nil, err 135 | } 136 | offset = offset + 8 137 | } 138 | 139 | // The MS-NLMP has this to say about the MIC 140 | // "An AUTHENTICATE_MESSAGE indicates the presence of a MIC field if the TargetInfo field has an AV_PAIR structure whose two fields are: 141 | // AvId == MsvAvFlags Value bit 0x2 == 1" 142 | // However there is no TargetInfo structure in the Authenticate Message! There is one in the Challenge Message though. So I'm using 143 | // 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 144 | // there is a MIC and read it out. 145 | var lowestOffset = am.getLowestPayloadOffset() 146 | if lowestOffset > offset { 147 | // MIC - 16 bytes 148 | am.Mic = body[offset : offset+16] 149 | offset = offset + 16 150 | } 151 | } 152 | 153 | am.Payload = body[offset:] 154 | 155 | return am, nil 156 | } 157 | 158 | func (a *AuthenticateMessage) ClientChallenge() (response []byte) { 159 | if a.NtlmV2Response != nil { 160 | response = a.NtlmV2Response.NtlmV2ClientChallenge.ChallengeFromClient 161 | } else if a.NtlmV1Response != nil && NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(a.NegotiateFlags) { 162 | response = a.LmV1Response.Response[0:8] 163 | } 164 | 165 | return response 166 | } 167 | 168 | func (a *AuthenticateMessage) getLowestPayloadOffset() int { 169 | payloadStructs := [...]*PayloadStruct{a.LmChallengeResponse, a.NtChallengeResponseFields, a.DomainName, a.UserName, a.Workstation, a.EncryptedRandomSessionKey} 170 | 171 | // Find the lowest offset value 172 | lowest := 9999 173 | for i := range payloadStructs { 174 | p := payloadStructs[i] 175 | if p != nil && p.Offset > 0 && int(p.Offset) < lowest { 176 | lowest = int(p.Offset) 177 | } 178 | } 179 | 180 | return lowest 181 | } 182 | 183 | func (a *AuthenticateMessage) Bytes() []byte { 184 | payloadLen := int(a.LmChallengeResponse.Len + a.NtChallengeResponseFields.Len + a.DomainName.Len + a.UserName.Len + a.Workstation.Len + a.EncryptedRandomSessionKey.Len) 185 | messageLen := 8 + 4 + 6*8 + 4 + 8 + 16 186 | payloadOffset := uint32(messageLen) 187 | 188 | messageBytes := make([]byte, 0, messageLen+payloadLen) 189 | buffer := bytes.NewBuffer(messageBytes) 190 | 191 | buffer.Write(a.Signature) 192 | 193 | binary.Write(buffer, binary.LittleEndian, a.MessageType) 194 | 195 | a.LmChallengeResponse.Offset = payloadOffset 196 | payloadOffset += uint32(a.LmChallengeResponse.Len) 197 | buffer.Write(a.LmChallengeResponse.Bytes()) 198 | 199 | a.NtChallengeResponseFields.Offset = payloadOffset 200 | payloadOffset += uint32(a.NtChallengeResponseFields.Len) 201 | buffer.Write(a.NtChallengeResponseFields.Bytes()) 202 | 203 | a.DomainName.Offset = payloadOffset 204 | payloadOffset += uint32(a.DomainName.Len) 205 | buffer.Write(a.DomainName.Bytes()) 206 | 207 | a.UserName.Offset = payloadOffset 208 | payloadOffset += uint32(a.UserName.Len) 209 | buffer.Write(a.UserName.Bytes()) 210 | 211 | a.Workstation.Offset = payloadOffset 212 | payloadOffset += uint32(a.Workstation.Len) 213 | buffer.Write(a.Workstation.Bytes()) 214 | 215 | a.EncryptedRandomSessionKey.Offset = payloadOffset 216 | payloadOffset += uint32(a.EncryptedRandomSessionKey.Len) 217 | buffer.Write(a.EncryptedRandomSessionKey.Bytes()) 218 | 219 | buffer.Write(uint32ToBytes(a.NegotiateFlags)) 220 | 221 | if a.Version != nil { 222 | buffer.Write(a.Version.Bytes()) 223 | } else { 224 | buffer.Write(make([]byte, 8)) 225 | } 226 | 227 | if a.Mic != nil { 228 | buffer.Write(a.Mic) 229 | } else { 230 | buffer.Write(make([]byte, 16)) 231 | } 232 | 233 | // Write out the payloads 234 | buffer.Write(a.LmChallengeResponse.Payload) 235 | buffer.Write(a.NtChallengeResponseFields.Payload) 236 | buffer.Write(a.DomainName.Payload) 237 | buffer.Write(a.UserName.Payload) 238 | buffer.Write(a.Workstation.Payload) 239 | buffer.Write(a.EncryptedRandomSessionKey.Payload) 240 | 241 | return buffer.Bytes() 242 | } 243 | 244 | func (a *AuthenticateMessage) String() string { 245 | var buffer bytes.Buffer 246 | 247 | buffer.WriteString("Authenticate NTLM Message\n") 248 | buffer.WriteString(fmt.Sprintf("Payload Offset: %d Length: %d\n", a.getLowestPayloadOffset(), len(a.Payload))) 249 | 250 | if a.LmV2Response != nil { 251 | buffer.WriteString(a.LmV2Response.String()) 252 | buffer.WriteString("\n") 253 | } 254 | 255 | if a.LmV1Response != nil { 256 | buffer.WriteString(a.LmV1Response.String()) 257 | buffer.WriteString("\n") 258 | } 259 | 260 | if a.NtlmV2Response != nil { 261 | buffer.WriteString(a.NtlmV2Response.String()) 262 | buffer.WriteString("\n") 263 | } 264 | 265 | if a.NtlmV1Response != nil { 266 | buffer.WriteString(fmt.Sprintf("NtlmResponse Length: %d\n", a.NtChallengeResponseFields.Len)) 267 | buffer.WriteString(a.NtlmV1Response.String()) 268 | buffer.WriteString("\n") 269 | } 270 | 271 | buffer.WriteString(fmt.Sprintf("UserName: %s\n", a.UserName.String())) 272 | buffer.WriteString(fmt.Sprintf("DomainName: %s\n", a.DomainName.String())) 273 | buffer.WriteString(fmt.Sprintf("Workstation: %s\n", a.Workstation.String())) 274 | 275 | if a.EncryptedRandomSessionKey != nil { 276 | buffer.WriteString(fmt.Sprintf("EncryptedRandomSessionKey: %s\n", a.EncryptedRandomSessionKey.String())) 277 | } 278 | 279 | if a.Version != nil { 280 | buffer.WriteString(fmt.Sprintf("Version: %s\n", a.Version.String())) 281 | } 282 | 283 | if a.Mic != nil { 284 | buffer.WriteString(fmt.Sprintf("MIC: %s\n", hex.EncodeToString(a.Mic))) 285 | } 286 | 287 | buffer.WriteString(fmt.Sprintf("Flags %d\n", a.NegotiateFlags)) 288 | buffer.WriteString(FlagsToString(a.NegotiateFlags)) 289 | 290 | return buffer.String() 291 | } 292 | -------------------------------------------------------------------------------- /ntlm/ntlmv1_test.go: -------------------------------------------------------------------------------- 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/base64" 8 | "encoding/hex" 9 | "testing" 10 | ) 11 | 12 | func TestLMOWFv1(t *testing.T) { 13 | // Sample from MS-NLMP 14 | result, err := lmowfv1("Password") 15 | expected, _ := hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d") 16 | if err != nil || !bytes.Equal(result, expected) { 17 | t.Errorf("LMNOWFv1 is not correct, got %s expected %s", hex.EncodeToString(result), "e52cac67419a9a224a3b108f3fa6cb6d") 18 | } 19 | } 20 | 21 | func TestNTOWFv1(t *testing.T) { 22 | // Sample from MS-NLMP 23 | result := ntowfv1("Password") 24 | expected, _ := hex.DecodeString("a4f49c406510bdcab6824ee7c30fd852") 25 | if !bytes.Equal(result, expected) { 26 | t.Error("NTOWFv1 is not correct") 27 | } 28 | } 29 | 30 | func checkV1Value(t *testing.T, name string, value []byte, expected string, err error) { 31 | if err != nil { 32 | t.Errorf("NTLMv1 %s received error: %s", name, err) 33 | } else { 34 | expectedBytes, _ := hex.DecodeString(expected) 35 | if !bytes.Equal(expectedBytes, value) { 36 | t.Errorf("NTLMv1 %s is not correct got %s expected %s", name, hex.EncodeToString(value), expected) 37 | } 38 | } 39 | } 40 | 41 | // There was an issue where all NTLMv1 authentications with extended session security 42 | // would authenticate. This was due to a bug in the MS-NLMP docs. This tests for that issue 43 | func TestNtlmV1ExtendedSessionSecurity(t *testing.T) { 44 | // NTLMv1 with extended session security 45 | challengeMessage := "TlRMTVNTUAACAAAAAAAAADgAAABVgphiRy3oSZvn1I4AAAAAAAAAAKIAogA4AAAABQEoCgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA=" 46 | authenticateMessage := "TlRMTVNTUAADAAAAGAAYAJgAAAAYABgAsAAAAAAAAABIAAAAOgA6AEgAAAAWABYAggAAABAAEADIAAAAVYKYYgUCzg4AAAAPMQAwADAAMAAwADEALgB3AGMAcABAAHQAaABvAG0AcwBvAG4AcgBlAHUAdABlAHIAcwAuAGMAbwBtAE4AWQBDAFMATQBTAEcAOQA5ADAAOQBRWAK3h/TIywAAAAAAAAAAAAAAAAAAAAA3tp89kZU1hs1XZp7KTyGm3XsFAT9stEDW9YXDaeYVBmBcBb//2FOu" 47 | 48 | challengeData, _ := base64.StdEncoding.DecodeString(challengeMessage) 49 | c, _ := ParseChallengeMessage(challengeData) 50 | 51 | authenticateData, _ := base64.StdEncoding.DecodeString(authenticateMessage) 52 | msg, err := ParseAuthenticateMessage(authenticateData, 1) 53 | if err != nil { 54 | t.Errorf("Could not process authenticate message: %s", err) 55 | } 56 | 57 | context, err := CreateServerSession(Version1, ConnectionlessMode) 58 | if err != nil { 59 | t.Errorf("Could not create NTLMv1 session") 60 | } 61 | context.SetUserInfo("100001.wcp.thomsonreuters.com", "notmypass", "") 62 | context.SetServerChallenge(c.ServerChallenge) 63 | err = context.ProcessAuthenticateMessage(msg) 64 | if err == nil { 65 | t.Errorf("This message should have failed to authenticate, but it passed", err) 66 | } 67 | } 68 | 69 | func TestNtlmV1(t *testing.T) { 70 | flags := uint32(0) 71 | flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) 72 | flags = NTLMSSP_NEGOTIATE_56.Set(flags) 73 | flags = NTLMSSP_NEGOTIATE_128.Set(flags) 74 | flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags) 75 | flags = NTLMSSP_TARGET_TYPE_SERVER.Set(flags) 76 | flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) 77 | flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags) 78 | flags = NTLMSSP_NEGOTIATE_SEAL.Set(flags) 79 | flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags) 80 | flags = NTLM_NEGOTIATE_OEM.Set(flags) 81 | flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags) 82 | 83 | n := new(V1ClientSession) 84 | n.SetUserInfo("User", "Password", "Domain") 85 | n.NegotiateFlags = flags 86 | n.responseKeyNT, _ = hex.DecodeString("a4f49c406510bdcab6824ee7c30fd852") 87 | n.responseKeyLM, _ = hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d") 88 | n.clientChallenge, _ = hex.DecodeString("aaaaaaaaaaaaaaaa") 89 | n.serverChallenge, _ = hex.DecodeString("0123456789abcdef") 90 | 91 | var err error 92 | // 4.2.2.1.3 Session Base Key and Key Exchange Key 93 | err = n.computeSessionBaseKey() 94 | checkV1Value(t, "sessionBaseKey", n.sessionBaseKey, "d87262b0cde4b1cb7499becccdf10784", err) 95 | err = n.computeKeyExchangeKey() 96 | checkV1Value(t, "keyExchangeKey", n.keyExchangeKey, "d87262b0cde4b1cb7499becccdf10784", err) 97 | 98 | // 4.2.2.2.1 NTLMv1 Response 99 | // NTChallengeResponse with With NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY not set 100 | err = n.computeExpectedResponses() 101 | checkV1Value(t, "NTChallengeResponse", n.ntChallengeResponse, "67c43011f30298a2ad35ece64f16331c44bdbed927841f94", err) 102 | // 4.2.2.2.2 LMv1 Response 103 | // The LmChallengeResponse is specified in section 3.3.1. With the NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag 104 | // not set and with the NoLMResponseNTLMv1 flag not set 105 | checkV1Value(t, "LMChallengeResponse", n.lmChallengeResponse, "98def7b87f88aa5dafe2df779688a172def11c7d5ccdef13", err) 106 | 107 | // If the NTLMSSP_NEGOTIATE_LM_KEY flag is set then the KeyExchangeKey is: 108 | n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Set(n.NegotiateFlags) 109 | err = n.computeKeyExchangeKey() 110 | checkV1Value(t, "keyExchangeKey with NTLMSSP_NEGOTIATE_LM_KEY", n.keyExchangeKey, "b09e379f7fbecb1eaf0afdcb0383c8a0", err) 111 | n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Unset(n.NegotiateFlags) 112 | 113 | // 4.2.2.2.3 Encrypted Session Key 114 | //n.randomSessionKey, _ = hex.DecodeString("55555555555555555555555555555555") 115 | 116 | // RC4 decryption of the EncryptedRandomSessionKey with the KeyExchange key 117 | //err = n.computeKeyExchangeKey() 118 | //n.encryptedRandomSessionKey, err = hex.DecodeString("518822b1b3f350c8958682ecbb3e3cb7") 119 | //err = n.computeExportedSessionKey() 120 | //checkV1Value(t, "ExportedSessionKey", n.exportedSessionKey, "55555555555555555555555555555555", err) 121 | 122 | // NTLMSSP_REQUEST_NON_NT_SESSION_KEY is set: 123 | n.NegotiateFlags = NTLMSSP_REQUEST_NON_NT_SESSION_KEY.Set(n.NegotiateFlags) 124 | err = n.computeKeyExchangeKey() 125 | // n.encryptedRandomSessionKey, err = hex.DecodeString("7452ca55c225a1ca04b48fae32cf56fc") 126 | // err = n.computeExportedSessionKey() 127 | // checkV1Value(t, "ExportedSessionKey - NTLMSSP_REQUEST_NON_NT_SESSION_KEY", n.exportedSessionKey, "55555555555555555555555555555555", err) 128 | n.NegotiateFlags = NTLMSSP_REQUEST_NON_NT_SESSION_KEY.Unset(n.NegotiateFlags) 129 | 130 | // NTLMSSP_NEGOTIATE_LM_KEY is set: 131 | n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Set(n.NegotiateFlags) 132 | err = n.computeKeyExchangeKey() 133 | // n.encryptedRandomSessionKey, err = hex.DecodeString("4cd7bb57d697ef9b549f02b8f9b37864") 134 | // err = n.computeExportedSessionKey() 135 | // checkV1Value(t, "ExportedSessionKey - NTLMSSP_NEGOTIATE_LM_KEY", n.exportedSessionKey, "55555555555555555555555555555555", err) 136 | n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Unset(n.NegotiateFlags) 137 | 138 | // 4.2.2.3 Messages 139 | challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033820a820123456789abcdef00000000000000000000000000000000060070170000000f530065007200760065007200") 140 | challengeMessage, err := ParseChallengeMessage(challengeMessageBytes) 141 | if err == nil { 142 | challengeMessage.String() 143 | } else { 144 | t.Errorf("Could not parse challenge message: %s", err) 145 | } 146 | 147 | client := new(V1ClientSession) 148 | client.SetUserInfo("User", "Password", "Domain") 149 | err = client.ProcessChallengeMessage(challengeMessage) 150 | if err != nil { 151 | t.Errorf("Could not process challenge message: %s", err) 152 | } 153 | 154 | server := new(V1ServerSession) 155 | server.SetUserInfo("User", "Password", "Domain") 156 | authenticateMessageBytes, err := hex.DecodeString("4e544c4d5353500003000000180018006c00000018001800840000000c000c00480000000800080054000000100010005c000000100010009c000000358280e20501280a0000000f44006f006d00610069006e00550073006500720043004f004d005000550054004500520098def7b87f88aa5dafe2df779688a172def11c7d5ccdef1367c43011f30298a2ad35ece64f16331c44bdbed927841f94518822b1b3f350c8958682ecbb3e3cb7") 157 | authenticateMessage, err := ParseAuthenticateMessage(authenticateMessageBytes, 1) 158 | if err == nil { 159 | authenticateMessage.String() 160 | } else { 161 | t.Errorf("Could not parse authenticate message: %s", err) 162 | } 163 | 164 | server = new(V1ServerSession) 165 | server.SetUserInfo("User", "Password", "Domain") 166 | server.serverChallenge = challengeMessage.ServerChallenge 167 | 168 | err = server.ProcessAuthenticateMessage(authenticateMessage) 169 | if err != nil { 170 | t.Errorf("Could not process authenticate message: %s", err) 171 | } 172 | } 173 | 174 | func TestNTLMv1WithClientChallenge(t *testing.T) { 175 | flags := uint32(0) 176 | flags = NTLMSSP_NEGOTIATE_56.Set(flags) 177 | flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags) 178 | flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) 179 | flags = NTLMSSP_TARGET_TYPE_SERVER.Set(flags) 180 | flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) 181 | flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags) 182 | flags = NTLMSSP_NEGOTIATE_SEAL.Set(flags) 183 | flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags) 184 | flags = NTLM_NEGOTIATE_OEM.Set(flags) 185 | flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags) 186 | 187 | n := new(V1Session) 188 | n.NegotiateFlags = flags 189 | n.responseKeyNT, _ = hex.DecodeString("a4f49c406510bdcab6824ee7c30fd852") 190 | n.responseKeyLM, _ = hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d") 191 | n.clientChallenge, _ = hex.DecodeString("aaaaaaaaaaaaaaaa") 192 | n.serverChallenge, _ = hex.DecodeString("0123456789abcdef") 193 | 194 | var err error 195 | // 4.2.2.1.3 Session Base Key and Key Exchange Key 196 | err = n.computeExpectedResponses() 197 | err = n.computeSessionBaseKey() 198 | checkV1Value(t, "sessionBaseKey", n.sessionBaseKey, "d87262b0cde4b1cb7499becccdf10784", err) 199 | checkV1Value(t, "LMv1Response", n.lmChallengeResponse, "aaaaaaaaaaaaaaaa00000000000000000000000000000000", err) 200 | checkV1Value(t, "NTLMv1Response", n.ntChallengeResponse, "7537f803ae367128ca458204bde7caf81e97ed2683267232", err) 201 | err = n.computeKeyExchangeKey() 202 | checkV1Value(t, "keyExchangeKey", n.keyExchangeKey, "eb93429a8bd952f8b89c55b87f475edc", err) 203 | 204 | challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033820a820123456789abcdef00000000000000000000000000000000060070170000000f530065007200760065007200") 205 | challengeMessage, err := ParseChallengeMessage(challengeMessageBytes) 206 | if err == nil { 207 | challengeMessage.String() 208 | } else { 209 | t.Errorf("Could not parse challenge message: %s", err) 210 | } 211 | 212 | client := new(V1ClientSession) 213 | client.SetUserInfo("User", "Password", "Domain") 214 | err = client.ProcessChallengeMessage(challengeMessage) 215 | if err != nil { 216 | t.Errorf("Could not process challenge message: %s", err) 217 | } 218 | 219 | server := new(V1ServerSession) 220 | server.SetUserInfo("User", "Password", "Domain") 221 | server.serverChallenge = challengeMessage.ServerChallenge 222 | 223 | authenticateMessageBytes, _ := hex.DecodeString("4e544c4d5353500003000000180018006c00000018001800840000000c000c00480000000800080054000000100010005c000000000000009c000000358208820501280a0000000f44006f006d00610069006e00550073006500720043004f004d0050005500540045005200aaaaaaaaaaaaaaaa000000000000000000000000000000007537f803ae367128ca458204bde7caf81e97ed2683267232") 224 | authenticateMessage, err := ParseAuthenticateMessage(authenticateMessageBytes, 1) 225 | if err == nil { 226 | authenticateMessage.String() 227 | } else { 228 | t.Errorf("Could not parse authenticate message: %s", err) 229 | } 230 | 231 | err = server.ProcessAuthenticateMessage(authenticateMessage) 232 | if err != nil { 233 | t.Errorf("Could not process authenticate message: %s", err) 234 | } 235 | 236 | checkV1Value(t, "SealKey", server.ClientSealingKey, "04dd7f014d8504d265a25cc86a3a7c06", nil) 237 | checkV1Value(t, "SignKey", server.ClientSigningKey, "60e799be5c72fc92922ae8ebe961fb8d", nil) 238 | } 239 | -------------------------------------------------------------------------------- /ntlm/negotiate_flags.go: -------------------------------------------------------------------------------- 1 | //Copyright 2013 Thomson Reuters Global Resources. BSD License please see License file for more information 2 | 3 | package ntlm 4 | 5 | // During NTLM authentication, each of the following flags is a possible value of the NegotiateFlags field of the NEGOTIATE_MESSAGE, 6 | // CHALLENGE_MESSAGE, and AUTHENTICATE_MESSAGE, unless otherwise noted. These flags define client or server NTLM capabilities 7 | // ssupported by the sender. 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "reflect" 13 | ) 14 | 15 | type NegotiateFlag uint32 16 | 17 | const ( 18 | // A (1 bit): If set, requests Unicode character set encoding. An alternate name for this field is NTLMSSP_NEGOTIATE_UNICODE. 19 | NTLMSSP_NEGOTIATE_UNICODE NegotiateFlag = 1 << iota 20 | // B (1 bit): If set, requests OEM character set encoding. An alternate name for this field is NTLM_NEGOTIATE_OEM. See bit A for details. 21 | NTLM_NEGOTIATE_OEM 22 | // The A and B bits are evaluated together as follows: 23 | // A==1: The choice of character set encoding MUST be Unicode. 24 | // A==0 and B==1: The choice of character set encoding MUST be OEM. 25 | // A==0 and B==0: The protocol MUST return SEC_E_INVALID_TOKEN. 26 | // C (1 bit): If set, a TargetName field of the CHALLENGE_MESSAGE (section 2.2.1.2) MUST be supplied. An alternate name for this field is NTLMSSP_REQUEST_TARGET. 27 | NTLMSSP_REQUEST_TARGET 28 | // r10 (1 bit): This bit is unused and MUST be zero. 29 | NTLMSSP_R10 30 | // D (1 bit): If set, requests session key negotiation for message signatures. If the client sends NTLMSSP_NEGOTIATE_SIGN to the server 31 | // in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SIGN to the client in the CHALLENGE_MESSAGE. An alternate name 32 | // for this field is NTLMSSP_NEGOTIATE_SIGN. 33 | NTLMSSP_NEGOTIATE_SIGN 34 | // E (1 bit): If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL 35 | // to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE. 36 | // Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD always set NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128, 37 | // if they are supported. An alternate name for this field is NTLMSSP_NEGOTIATE_SEAL. 38 | NTLMSSP_NEGOTIATE_SEAL 39 | // F (1 bit): If set, requests connectionless authentication. If NTLMSSP_NEGOTIATE_DATAGRAM is set, then NTLMSSP_NEGOTIATE_KEY_EXCH 40 | // MUST always be set in the AUTHENTICATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. An alternate name for 41 | // this field is NTLMSSP_NEGOTIATE_DATAGRAM. 42 | NTLMSSP_NEGOTIATE_DATAGRAM 43 | // G (1 bit): If set, requests LAN Manager (LM) session key computation. NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY 44 | // are mutually exclusive. If both NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are requested, 45 | // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client. NTLM v2 authentication session key generation 46 | // MUST be supported by both the client and the DC in order to be used, and extended session security signing and sealing requires 47 | // support from the client and the server to be used. An alternate name for this field is NTLMSSP_NEGOTIATE_LM_KEY. 48 | NTLMSSP_NEGOTIATE_LM_KEY 49 | // r9 (1 bit): This bit is unused and MUST be zero. 50 | NTLMSSP_R9 51 | // H (1 bit): If set, requests usage of the NTLM v1 session security protocol. NTLMSSP_NEGOTIATE_NTLM MUST be set in the 52 | // NEGOTIATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. An alternate name for this field is NTLMSSP_NEGOTIATE_NTLM. 53 | NTLMSSP_NEGOTIATE_NTLM 54 | // r8 (1 bit): This bit is unused and MUST be zero. 55 | NTLMSSP_R8 56 | // J (1 bit): If set, the connection SHOULD be anonymous.<26> r8 (1 bit): This bit is unused and SHOULD be zero.<27> 57 | NTLMSSP_ANONYMOUS 58 | // K (1 bit): If set, the domain name is provided (section 2.2.1.1).<25> An alternate name for this field is NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED. 59 | NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED 60 | // L (1 bit): This flag indicates whether the Workstation field is present. If this flag is not set, the Workstation field 61 | // MUST be ignored. If this flag is set, the length field of the Workstation field specifies whether the workstation name 62 | // is nonempty or not.<24> An alternate name for this field is NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED. 63 | NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED 64 | // r7 (1 bit): This bit is unused and MUST be zero. 65 | NTLMSSP_R7 66 | // M (1 bit): If set, requests the presence of a signature block on all NTLMSSP_NEGOTIATE_ALWAYS_SIGN MUST be 67 | // set in the NEGOTIATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. NTLMSSP_NEGOTIATE_ALWAYS_SIGN is 68 | // overridden by NTLMSSP_NEGOTIATE_SIGN and NTLMSSP_NEGOTIATE_SEAL, if they are supported. An alternate name for this field 69 | // is NTLMSSP_NEGOTIATE_ALWAYS_SIGN. 70 | NTLMSSP_NEGOTIATE_ALWAYS_SIGN 71 | // N (1 bit): If set, TargetName MUST be a domain name. The data corresponding to this flag is provided by the server in the 72 | // TargetName field of the CHALLENGE_MESSAGE. If set, then NTLMSSP_TARGET_TYPE_SERVER MUST NOT be set. This flag MUST be ignored 73 | // in the NEGOTIATE_MESSAGE and the AUTHENTICATE_MESSAGE. An alternate name for this field is NTLMSSP_TARGET_TYPE_DOMAIN. 74 | NTLMSSP_TARGET_TYPE_DOMAIN 75 | // O (1 bit): If set, TargetName MUST be a server name. The data corresponding to this flag is provided by the server in the 76 | // TargetName field of the CHALLENGE_MESSAGE. If this bit is set, then NTLMSSP_TARGET_TYPE_DOMAIN MUST NOT be set. This flag MUST 77 | // be ignored in the NEGOTIATE_MESSAGE and the AUTHENTICATE_MESSAGE. An alternate name for this field is NTLMSSP_TARGET_TYPE_SERVER. 78 | NTLMSSP_TARGET_TYPE_SERVER 79 | // r6 (1 bit): This bit is unused and MUST be zero. 80 | NTLMSSP_R6 81 | // P (1 bit): If set, requests usage of the NTLM v2 session security. NTLM v2 session security is a misnomer because it is not 82 | // NTLM v2. It is NTLM v1 using the extended session security that is also in NTLM v2. NTLMSSP_NEGOTIATE_LM_KEY and 83 | // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are mutually exclusive. If both NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and 84 | // NTLMSSP_NEGOTIATE_LM_KEY are requested, NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client. 85 | // NTLM v2 authentication session key generation MUST be supported by both the client and the DC in order to be used, and extended 86 | // session security signing and sealing requires support from the client and the server in order to be used.<23> An alternate name 87 | // for this field is NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY. 88 | NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY 89 | // Q (1 bit): If set, requests an identify level token. An alternate name for this field is NTLMSSP_NEGOTIATE_IDENTIFY. 90 | NTLMSSP_NEGOTIATE_IDENTIFY 91 | // r5 (1 bit): This bit is unused and MUST be zero. 92 | NTLMSSP_R5 93 | // R (1 bit): If set, requests the usage of the LMOWF (section 3.3). An alternate name for this field is NTLMSSP_REQUEST_NON_NT_SESSION_KEY. 94 | NTLMSSP_REQUEST_NON_NT_SESSION_KEY 95 | // S (1 bit): If set, indicates that the TargetInfo fields in the CHALLENGE_MESSAGE (section 2.2.1.2) are populated. An alternate 96 | // name for this field is NTLMSSP_NEGOTIATE_TARGET_INFO. 97 | NTLMSSP_NEGOTIATE_TARGET_INFO 98 | // r4 (1 bit): This bit is unused and MUST be zero. 99 | NTLMSSP_R4 100 | // T (1 bit): If set, requests the protocol version number. The data corresponding to this flag is provided in the Version field of the 101 | // NEGOTIATE_MESSAGE, the CHALLENGE_MESSAGE, and the AUTHENTICATE_MESSAGE.<22> An alternate name for this field is NTLMSSP_NEGOTIATE_VERSION. 102 | NTLMSSP_NEGOTIATE_VERSION 103 | // r3 (1 bit): This bit is unused and MUST be zero. 104 | NTLMSSP_R3 105 | // r2 (1 bit): This bit is unused and MUST be zero. 106 | NTLMSSP_R2 107 | // r1 (1 bit): This bit is unused and MUST be zero. 108 | NTLMSSP_R1 109 | // U (1 bit): If set, requests 128-bit session key negotiation. An alternate name for this field is NTLMSSP_NEGOTIATE_128. If the client 110 | // sends NTLMSSP_NEGOTIATE_128 to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_128 to the client in the 111 | // CHALLENGE_MESSAGE only if the client sets NTLMSSP_NEGOTIATE_SEAL or NTLMSSP_NEGOTIATE_SIGN. Otherwise it is ignored. If both 112 | // NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 are requested and supported by the client and server, NTLMSSP_NEGOTIATE_56 and 113 | // NTLMSSP_NEGOTIATE_128 will both be returned to the client. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD set 114 | // NTLMSSP_NEGOTIATE_128 if it is supported. An alternate name for this field is NTLMSSP_NEGOTIATE_128.<21> 115 | NTLMSSP_NEGOTIATE_128 116 | // V (1 bit): If set, requests an explicit key exchange. This capability SHOULD be used because it improves security for message integrity or 117 | // confidentiality. See sections 3.2.5.1.2, 3.2.5.2.1, and 3.2.5.2.2 for details. An alternate name for this field is NTLMSSP_NEGOTIATE_KEY_EXCH. 118 | NTLMSSP_NEGOTIATE_KEY_EXCH 119 | // If set, requests 56-bit encryption. If the client sends NTLMSSP_NEGOTIATE_SEAL or NTLMSSP_NEGOTIATE_SIGN with NTLMSSP_NEGOTIATE_56 to the 120 | // server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_56 to the client in the CHALLENGE_MESSAGE. Otherwise it is ignored. 121 | // If both NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 are requested and supported by the client and server, NTLMSSP_NEGOTIATE_56 and 122 | // NTLMSSP_NEGOTIATE_128 will both be returned to the client. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD set NTLMSSP_NEGOTIATE_56 123 | // if it is supported. An alternate name for this field is NTLMSSP_NEGOTIATE_56. 124 | NTLMSSP_NEGOTIATE_56 125 | ) 126 | 127 | func (f NegotiateFlag) Set(flags uint32) uint32 { 128 | return flags | uint32(f) 129 | } 130 | 131 | func (f NegotiateFlag) IsSet(flags uint32) bool { 132 | return (flags & uint32(f)) != 0 133 | } 134 | 135 | func (f NegotiateFlag) Unset(flags uint32) uint32 { 136 | return flags &^ uint32(f) 137 | } 138 | 139 | func (f NegotiateFlag) String() string { 140 | return reflect.TypeOf(f).Name() 141 | } 142 | 143 | func GetFlagName(flag NegotiateFlag) string { 144 | nameMap := map[NegotiateFlag]string{ 145 | NTLMSSP_NEGOTIATE_56: "NTLMSSP_NEGOTIATE_56", 146 | NTLMSSP_NEGOTIATE_KEY_EXCH: "NTLMSSP_NEGOTIATE_KEY_EXCH", 147 | NTLMSSP_NEGOTIATE_128: "NTLMSSP_NEGOTIATE_128", 148 | NTLMSSP_NEGOTIATE_VERSION: "NTLMSSP_NEGOTIATE_VERSION", 149 | NTLMSSP_NEGOTIATE_TARGET_INFO: "NTLMSSP_NEGOTIATE_TARGET_INFO", 150 | NTLMSSP_REQUEST_NON_NT_SESSION_KEY: "NTLMSSP_REQUEST_NON_NT_SESSION_KEY", 151 | NTLMSSP_NEGOTIATE_IDENTIFY: "NTLMSSP_NEGOTIATE_IDENTIFY", 152 | NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: "NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY", 153 | NTLMSSP_TARGET_TYPE_SERVER: "NTLMSSP_TARGET_TYPE_SERVER", 154 | NTLMSSP_TARGET_TYPE_DOMAIN: "NTLMSSP_TARGET_TYPE_DOMAIN", 155 | NTLMSSP_NEGOTIATE_ALWAYS_SIGN: "NTLMSSP_NEGOTIATE_ALWAYS_SIGN", 156 | NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED: "NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED", 157 | NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED: "NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED", 158 | NTLMSSP_ANONYMOUS: "NTLMSSP_ANONYMOUS", 159 | NTLMSSP_NEGOTIATE_NTLM: "NTLMSSP_NEGOTIATE_NTLM", 160 | NTLMSSP_NEGOTIATE_LM_KEY: "NTLMSSP_NEGOTIATE_LM_KEY", 161 | NTLMSSP_NEGOTIATE_DATAGRAM: "NTLMSSP_NEGOTIATE_DATAGRAM", 162 | NTLMSSP_NEGOTIATE_SEAL: "NTLMSSP_NEGOTIATE_SEAL", 163 | NTLMSSP_NEGOTIATE_SIGN: "NTLMSSP_NEGOTIATE_SIGN", 164 | NTLMSSP_REQUEST_TARGET: "NTLMSSP_REQUEST_TARGET", 165 | NTLM_NEGOTIATE_OEM: "NTLM_NEGOTIATE_OEM", 166 | NTLMSSP_NEGOTIATE_UNICODE: "NTLMSSP_NEGOTIATE_UNICODE"} 167 | 168 | return nameMap[flag] 169 | } 170 | 171 | func FlagsToString(flags uint32) string { 172 | allFlags := [...]NegotiateFlag{ 173 | NTLMSSP_NEGOTIATE_56, 174 | NTLMSSP_NEGOTIATE_KEY_EXCH, 175 | NTLMSSP_NEGOTIATE_128, 176 | NTLMSSP_NEGOTIATE_VERSION, 177 | NTLMSSP_NEGOTIATE_TARGET_INFO, 178 | NTLMSSP_REQUEST_NON_NT_SESSION_KEY, 179 | NTLMSSP_NEGOTIATE_IDENTIFY, 180 | NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY, 181 | NTLMSSP_TARGET_TYPE_SERVER, 182 | NTLMSSP_TARGET_TYPE_DOMAIN, 183 | NTLMSSP_NEGOTIATE_ALWAYS_SIGN, 184 | NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED, 185 | NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED, 186 | NTLMSSP_ANONYMOUS, 187 | NTLMSSP_NEGOTIATE_NTLM, 188 | NTLMSSP_NEGOTIATE_LM_KEY, 189 | NTLMSSP_NEGOTIATE_DATAGRAM, 190 | NTLMSSP_NEGOTIATE_SEAL, 191 | NTLMSSP_NEGOTIATE_SIGN, 192 | NTLMSSP_REQUEST_TARGET, 193 | NTLM_NEGOTIATE_OEM, 194 | NTLMSSP_NEGOTIATE_UNICODE} 195 | 196 | var buffer bytes.Buffer 197 | for i := range allFlags { 198 | f := allFlags[i] 199 | buffer.WriteString(fmt.Sprintf("%s: %v\n", GetFlagName(f), f.IsSet(flags))) 200 | } 201 | return buffer.String() 202 | } 203 | -------------------------------------------------------------------------------- /ntlm/ntlmv1.go: -------------------------------------------------------------------------------- 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 | "errors" 9 | "log" 10 | "strings" 11 | ) 12 | 13 | /******************************* 14 | Shared Session Data and Methods 15 | *******************************/ 16 | 17 | type V1Session struct { 18 | SessionData 19 | } 20 | 21 | func (n *V1Session) SetUserInfo(username string, password string, domain string) { 22 | n.user = username 23 | n.password = password 24 | n.userDomain = domain 25 | } 26 | 27 | func (n *V1Session) GetUserInfo() (string, string, string) { 28 | return n.user, n.password, n.userDomain 29 | } 30 | 31 | func (n *V1Session) SetMode(mode Mode) { 32 | n.mode = mode 33 | } 34 | 35 | func (n *V1Session) Version() int { 36 | return 1 37 | } 38 | 39 | func (n *V1Session) fetchResponseKeys() (err error) { 40 | n.responseKeyLM, err = lmowfv1(n.password) 41 | if err != nil { 42 | return err 43 | } 44 | n.responseKeyNT = ntowfv1(n.password) 45 | return 46 | } 47 | 48 | func (n *V1Session) computeExpectedResponses() (err error) { 49 | if NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(n.NegotiateFlags) { 50 | n.ntChallengeResponse, err = desL(n.responseKeyNT, md5(concat(n.serverChallenge, n.clientChallenge))[0:8]) 51 | if err != nil { 52 | return err 53 | } 54 | n.lmChallengeResponse = concat(n.clientChallenge, make([]byte, 16)) 55 | } else { 56 | n.ntChallengeResponse, err = desL(n.responseKeyNT, n.serverChallenge) 57 | if err != nil { 58 | return err 59 | } 60 | // NoLMResponseNTLMv1: A Boolean setting that controls using the NTLM response for the LM 61 | // response to the server challenge when NTLMv1 authentication is used.<30> 62 | // <30> Section 3.1.1.1: The default value of this state variable is TRUE. Windows NT Server 4.0 SP3 63 | // does not support providing NTLM instead of LM responses. 64 | noLmResponseNtlmV1 := false 65 | if noLmResponseNtlmV1 { 66 | n.lmChallengeResponse = n.ntChallengeResponse 67 | } else { 68 | n.lmChallengeResponse, err = desL(n.responseKeyLM, n.serverChallenge) 69 | if err != nil { 70 | return err 71 | } 72 | } 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (n *V1Session) computeSessionBaseKey() (err error) { 79 | n.sessionBaseKey = md4(n.responseKeyNT) 80 | return 81 | } 82 | 83 | func (n *V1Session) computeKeyExchangeKey() (err error) { 84 | if NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(n.NegotiateFlags) { 85 | n.keyExchangeKey = hmacMd5(n.sessionBaseKey, concat(n.serverChallenge, n.lmChallengeResponse[0:8])) 86 | } else { 87 | n.keyExchangeKey, err = kxKey(n.NegotiateFlags, n.sessionBaseKey, n.lmChallengeResponse, n.serverChallenge, n.responseKeyLM) 88 | } 89 | return 90 | } 91 | 92 | func (n *V1Session) calculateKeys(ntlmRevisionCurrent uint8) (err error) { 93 | // This lovely piece of code comes courtesy of an the excellent Open Document support system from MSFT 94 | // In order to calculate the keys correctly when the client has set the NTLMRevisionCurrent to 0xF (15) 95 | // We must treat the flags as if NTLMSSP_NEGOTIATE_LM_KEY is set. 96 | // This information is not contained (at least currently, until they correct it) in the MS-NLMP document 97 | if ntlmRevisionCurrent == 15 { 98 | n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Set(n.NegotiateFlags) 99 | } 100 | 101 | n.ClientSigningKey = signKey(n.NegotiateFlags, n.exportedSessionKey, "Client") 102 | n.ServerSigningKey = signKey(n.NegotiateFlags, n.exportedSessionKey, "Server") 103 | n.ClientSealingKey = sealKey(n.NegotiateFlags, n.exportedSessionKey, "Client") 104 | n.ServerSealingKey = sealKey(n.NegotiateFlags, n.exportedSessionKey, "Server") 105 | return 106 | } 107 | 108 | func (n *V1Session) Seal(message []byte) ([]byte, error) { 109 | return nil, nil 110 | } 111 | 112 | func (n *V1Session) Sign(message []byte) ([]byte, error) { 113 | return nil, nil 114 | } 115 | 116 | func ntlmV1Mac(message []byte, sequenceNumber int, handle *rc4P.Cipher, sealingKey, signingKey []byte, NegotiateFlags uint32) []byte { 117 | // TODO: Need to keep track of the sequence number for connection oriented NTLM 118 | if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) && NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(NegotiateFlags) { 119 | handle, _ = reinitSealingKey(sealingKey, sequenceNumber) 120 | } else if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) { 121 | // CONOR: Reinitializing the rc4 cipher on every requst, but not using the 122 | // algorithm as described in the MS-NTLM document. Just reinitialize it directly. 123 | handle, _ = rc4Init(sealingKey) 124 | } 125 | sig := mac(NegotiateFlags, handle, signingKey, uint32(sequenceNumber), message) 126 | return sig.Bytes() 127 | } 128 | 129 | func (n *V1ServerSession) Mac(message []byte, sequenceNumber int) ([]byte, error) { 130 | mac := ntlmV1Mac(message, sequenceNumber, n.serverHandle, n.ServerSealingKey, n.ServerSigningKey, n.NegotiateFlags) 131 | return mac, nil 132 | } 133 | 134 | func (n *V1ClientSession) Mac(message []byte, sequenceNumber int) ([]byte, error) { 135 | mac := ntlmV1Mac(message, sequenceNumber, n.clientHandle, n.ClientSealingKey, n.ClientSigningKey, n.NegotiateFlags) 136 | return mac, nil 137 | } 138 | 139 | func (n *V1ServerSession) VerifyMac(message, expectedMac []byte, sequenceNumber int) (bool, error) { 140 | mac := ntlmV1Mac(message, sequenceNumber, n.clientHandle, n.ClientSealingKey, n.ClientSigningKey, n.NegotiateFlags) 141 | return MacsEqual(mac, expectedMac), nil 142 | } 143 | 144 | func (n *V1ClientSession) VerifyMac(message, expectedMac []byte, sequenceNumber int) (bool, error) { 145 | mac := ntlmV1Mac(message, sequenceNumber, n.serverHandle, n.ServerSealingKey, n.ServerSigningKey, n.NegotiateFlags) 146 | return MacsEqual(mac, expectedMac), nil 147 | } 148 | 149 | /************** 150 | Server Session 151 | **************/ 152 | 153 | type V1ServerSession struct { 154 | V1Session 155 | } 156 | 157 | func (n *V1ServerSession) ProcessNegotiateMessage(nm *NegotiateMessage) (err error) { 158 | n.negotiateMessage = nm 159 | return 160 | } 161 | 162 | func (n *V1ServerSession) GenerateChallengeMessage() (cm *ChallengeMessage, err error) { 163 | // TODO: Generate this challenge message 164 | return 165 | } 166 | 167 | func (n *V1ServerSession) SetServerChallenge(challenge []byte) { 168 | n.serverChallenge = challenge 169 | } 170 | 171 | func (n *V1ServerSession) GetSessionData() *SessionData { 172 | return &n.SessionData 173 | } 174 | 175 | func (n *V1ServerSession) ProcessAuthenticateMessage(am *AuthenticateMessage) (err error) { 176 | n.authenticateMessage = am 177 | n.NegotiateFlags = am.NegotiateFlags 178 | n.clientChallenge = am.ClientChallenge() 179 | n.encryptedRandomSessionKey = am.EncryptedRandomSessionKey.Payload 180 | // Ignore the values used in SetUserInfo and use these instead from the authenticate message 181 | // They should always be correct (I hope) 182 | n.user = am.UserName.String() 183 | n.userDomain = am.DomainName.String() 184 | log.Printf("(ProcessAuthenticateMessage)NTLM v1 User %s Domain %s", n.user, n.userDomain) 185 | 186 | err = n.fetchResponseKeys() 187 | if err != nil { 188 | return err 189 | } 190 | 191 | err = n.computeExpectedResponses() 192 | if err != nil { 193 | return err 194 | } 195 | 196 | err = n.computeSessionBaseKey() 197 | if err != nil { 198 | return err 199 | } 200 | 201 | err = n.computeKeyExchangeKey() 202 | if err != nil { 203 | return err 204 | } 205 | 206 | if !bytes.Equal(am.NtChallengeResponseFields.Payload, n.ntChallengeResponse) { 207 | // There is a bug with the steps in MS-NLMP. In section 3.2.5.1.2 it says you should fall through 208 | // to compare the lmChallengeResponse if the ntChallengeRepsonse fails, but with extended session security 209 | // this would *always* pass because the lmChallengeResponse and expectedLmChallengeRepsonse will always 210 | // be the same 211 | if !bytes.Equal(am.LmChallengeResponse.Payload, n.lmChallengeResponse) || NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(n.NegotiateFlags) { 212 | return errors.New("Could not authenticate") 213 | } 214 | } 215 | 216 | n.mic = am.Mic 217 | am.Mic = zeroBytes(16) 218 | 219 | err = n.computeExportedSessionKey() 220 | if err != nil { 221 | return err 222 | } 223 | 224 | if am.Version == nil { 225 | //UGH not entirely sure how this could possibly happen, going to put this in for now 226 | //TODO investigate if this ever is really happening 227 | am.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} 228 | log.Printf("Nil version in ntlmv1") 229 | } 230 | 231 | err = n.calculateKeys(am.Version.NTLMRevisionCurrent) 232 | if err != nil { 233 | return err 234 | } 235 | 236 | n.clientHandle, err = rc4Init(n.ClientSealingKey) 237 | if err != nil { 238 | return err 239 | } 240 | n.serverHandle, err = rc4Init(n.ServerSealingKey) 241 | if err != nil { 242 | return err 243 | } 244 | 245 | return nil 246 | } 247 | 248 | func (n *V1ServerSession) computeExportedSessionKey() (err error) { 249 | if NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.NegotiateFlags) { 250 | n.exportedSessionKey, err = rc4K(n.keyExchangeKey, n.encryptedRandomSessionKey) 251 | if err != nil { 252 | return err 253 | } 254 | // TODO: Calculate mic correctly. This calculation is not producing the right results now 255 | // n.calculatedMic = HmacMd5(n.exportedSessionKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes)) 256 | } else { 257 | n.exportedSessionKey = n.keyExchangeKey 258 | // TODO: Calculate mic correctly. This calculation is not producing the right results now 259 | // n.calculatedMic = HmacMd5(n.keyExchangeKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes)) 260 | } 261 | return nil 262 | } 263 | 264 | /************* 265 | Client Session 266 | **************/ 267 | 268 | type V1ClientSession struct { 269 | V1Session 270 | } 271 | 272 | func (n *V1ClientSession) GenerateNegotiateMessage() (nm *NegotiateMessage, err error) { 273 | return nil, nil 274 | } 275 | 276 | func (n *V1ClientSession) ProcessChallengeMessage(cm *ChallengeMessage) (err error) { 277 | n.challengeMessage = cm 278 | n.serverChallenge = cm.ServerChallenge 279 | n.clientChallenge = randomBytes(8) 280 | 281 | // Set up the default flags for processing the response. These are the flags that we will return 282 | // in the authenticate message 283 | flags := uint32(0) 284 | flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) 285 | // NOTE: Unsetting this flag in order to get the server to generate the signatures we can recognize 286 | flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags) 287 | flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) 288 | flags = NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags) 289 | flags = NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags) 290 | flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) 291 | flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags) 292 | flags = NTLMSSP_NEGOTIATE_DATAGRAM.Set(flags) 293 | flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags) 294 | flags = NTLMSSP_REQUEST_TARGET.Set(flags) 295 | flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags) 296 | 297 | n.NegotiateFlags = flags 298 | 299 | err = n.fetchResponseKeys() 300 | if err != nil { 301 | return err 302 | } 303 | 304 | err = n.computeExpectedResponses() 305 | if err != nil { 306 | return err 307 | } 308 | 309 | err = n.computeSessionBaseKey() 310 | if err != nil { 311 | return err 312 | } 313 | 314 | err = n.computeKeyExchangeKey() 315 | if err != nil { 316 | return err 317 | } 318 | 319 | err = n.computeEncryptedSessionKey() 320 | if err != nil { 321 | return err 322 | } 323 | 324 | err = n.calculateKeys(cm.Version.NTLMRevisionCurrent) 325 | if err != nil { 326 | return err 327 | } 328 | 329 | n.clientHandle, err = rc4Init(n.ClientSealingKey) 330 | if err != nil { 331 | return err 332 | } 333 | n.serverHandle, err = rc4Init(n.ServerSealingKey) 334 | if err != nil { 335 | return err 336 | } 337 | 338 | return nil 339 | } 340 | 341 | func (n *V1ClientSession) GenerateAuthenticateMessage() (am *AuthenticateMessage, err error) { 342 | am = new(AuthenticateMessage) 343 | am.Signature = []byte("NTLMSSP\x00") 344 | am.MessageType = uint32(3) 345 | am.LmChallengeResponse, _ = CreateBytePayload(n.lmChallengeResponse) 346 | am.NtChallengeResponseFields, _ = CreateBytePayload(n.ntChallengeResponse) 347 | am.DomainName, _ = CreateStringPayload(n.userDomain) 348 | am.UserName, _ = CreateStringPayload(n.user) 349 | am.Workstation, _ = CreateStringPayload("SQUAREMILL") 350 | am.EncryptedRandomSessionKey, _ = CreateBytePayload(n.encryptedRandomSessionKey) 351 | am.NegotiateFlags = n.NegotiateFlags 352 | am.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} 353 | return am, nil 354 | } 355 | 356 | func (n *V1ClientSession) computeEncryptedSessionKey() (err error) { 357 | if NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.NegotiateFlags) { 358 | n.exportedSessionKey = randomBytes(16) 359 | n.encryptedRandomSessionKey, err = rc4K(n.keyExchangeKey, n.exportedSessionKey) 360 | if err != nil { 361 | return err 362 | } 363 | } else { 364 | n.encryptedRandomSessionKey = n.keyExchangeKey 365 | } 366 | return nil 367 | } 368 | 369 | /******************************** 370 | NTLM V1 Password hash functions 371 | *********************************/ 372 | 373 | func ntowfv1(passwd string) []byte { 374 | return md4(utf16FromString(passwd)) 375 | } 376 | 377 | // ConcatenationOf( DES( UpperCase( Passwd)[0..6],"KGS!@#$%"), DES( UpperCase( Passwd)[7..13],"KGS!@#$%")) 378 | func lmowfv1(passwd string) ([]byte, error) { 379 | asciiPassword := []byte(strings.ToUpper(passwd)) 380 | keyBytes := zeroPaddedBytes(asciiPassword, 0, 14) 381 | 382 | first, err := des(keyBytes[0:7], []byte("KGS!@#$%")) 383 | if err != nil { 384 | return nil, err 385 | } 386 | second, err := des(keyBytes[7:14], []byte("KGS!@#$%")) 387 | if err != nil { 388 | return nil, err 389 | } 390 | 391 | return append(first, second...), nil 392 | } 393 | -------------------------------------------------------------------------------- /ntlm/ntlmv2.go: -------------------------------------------------------------------------------- 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 | "log" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | /******************************* 16 | Shared Session Data and Methods 17 | *******************************/ 18 | 19 | type V2Session struct { 20 | SessionData 21 | } 22 | 23 | func (n *V2Session) SetUserInfo(username string, password string, domain string) { 24 | n.user = username 25 | n.password = password 26 | n.userDomain = domain 27 | } 28 | 29 | func (n *V2Session) GetUserInfo() (string, string, string) { 30 | return n.user, n.password, n.userDomain 31 | } 32 | 33 | func (n *V2Session) SetMode(mode Mode) { 34 | n.mode = mode 35 | } 36 | 37 | func (n *V2Session) Version() int { 38 | return 2 39 | } 40 | 41 | func (n *V2Session) fetchResponseKeys() (err error) { 42 | // Usually at this point we'd go out to Active Directory and get these keys 43 | // Here we are assuming we have the information locally 44 | n.responseKeyLM = lmowfv2(n.user, n.password, n.userDomain) 45 | n.responseKeyNT = ntowfv2(n.user, n.password, n.userDomain) 46 | return 47 | } 48 | 49 | func (n *V2ServerSession) GetSessionData() *SessionData { 50 | return &n.SessionData 51 | } 52 | 53 | // Define ComputeResponse(NegFlg, ResponseKeyNT, ResponseKeyLM, CHALLENGE_MESSAGE.ServerChallenge, ClientChallenge, Time, ServerName) 54 | // ServerNameBytes - The NtChallengeResponseFields.NTLMv2_RESPONSE.NTLMv2_CLIENT_CHALLENGE.AvPairs field structure of the AUTHENTICATE_MESSAGE payload. 55 | func (n *V2Session) computeExpectedResponses(timestamp []byte, avPairBytes []byte) (err error) { 56 | temp := concat([]byte{0x01}, []byte{0x01}, zeroBytes(6), timestamp, n.clientChallenge, zeroBytes(4), avPairBytes, zeroBytes(4)) 57 | ntProofStr := hmacMd5(n.responseKeyNT, concat(n.serverChallenge, temp)) 58 | n.ntChallengeResponse = concat(ntProofStr, temp) 59 | n.lmChallengeResponse = concat(hmacMd5(n.responseKeyLM, concat(n.serverChallenge, n.clientChallenge)), n.clientChallenge) 60 | n.sessionBaseKey = hmacMd5(n.responseKeyNT, ntProofStr) 61 | return 62 | } 63 | 64 | func (n *V2Session) computeKeyExchangeKey() (err error) { 65 | n.keyExchangeKey = n.sessionBaseKey 66 | return 67 | } 68 | 69 | func (n *V2Session) calculateKeys(ntlmRevisionCurrent uint8) (err error) { 70 | // This lovely piece of code comes courtesy of an the excellent Open Document support system from MSFT 71 | // In order to calculate the keys correctly when the client has set the NTLMRevisionCurrent to 0xF (15) 72 | // We must treat the flags as if NTLMSSP_NEGOTIATE_LM_KEY is set. 73 | // This information is not contained (at least currently, until they correct it) in the MS-NLMP document 74 | if ntlmRevisionCurrent == 15 { 75 | n.NegotiateFlags = NTLMSSP_NEGOTIATE_LM_KEY.Set(n.NegotiateFlags) 76 | } 77 | 78 | n.ClientSigningKey = signKey(n.NegotiateFlags, n.exportedSessionKey, "Client") 79 | n.ServerSigningKey = signKey(n.NegotiateFlags, n.exportedSessionKey, "Server") 80 | n.ClientSealingKey = sealKey(n.NegotiateFlags, n.exportedSessionKey, "Client") 81 | n.ServerSealingKey = sealKey(n.NegotiateFlags, n.exportedSessionKey, "Server") 82 | return 83 | } 84 | 85 | func (n *V2Session) Seal(message []byte) ([]byte, error) { 86 | return nil, nil 87 | } 88 | func (n *V2Session) Sign(message []byte) ([]byte, error) { 89 | return nil, nil 90 | } 91 | 92 | //Mildly ghetto that we expose this 93 | func NtlmVCommonMac(message []byte, sequenceNumber int, sealingKey, signingKey []byte, NegotiateFlags uint32) []byte { 94 | var handle *rc4P.Cipher 95 | // TODO: Need to keep track of the sequence number for connection oriented NTLM 96 | if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) && NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(NegotiateFlags) { 97 | handle, _ = reinitSealingKey(sealingKey, sequenceNumber) 98 | } else if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) { 99 | // CONOR: Reinitializing the rc4 cipher on every requst, but not using the 100 | // algorithm as described in the MS-NTLM document. Just reinitialize it directly. 101 | handle, _ = rc4Init(sealingKey) 102 | } 103 | sig := mac(NegotiateFlags, handle, signingKey, uint32(sequenceNumber), message) 104 | return sig.Bytes() 105 | } 106 | 107 | func NtlmV2Mac(message []byte, sequenceNumber int, handle *rc4P.Cipher, sealingKey, signingKey []byte, NegotiateFlags uint32) []byte { 108 | // TODO: Need to keep track of the sequence number for connection oriented NTLM 109 | if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) && NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(NegotiateFlags) { 110 | handle, _ = reinitSealingKey(sealingKey, sequenceNumber) 111 | } else if NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(NegotiateFlags) { 112 | // CONOR: Reinitializing the rc4 cipher on every requst, but not using the 113 | // algorithm as described in the MS-NTLM document. Just reinitialize it directly. 114 | handle, _ = rc4Init(sealingKey) 115 | } 116 | sig := mac(NegotiateFlags, handle, signingKey, uint32(sequenceNumber), message) 117 | return sig.Bytes() 118 | } 119 | 120 | func (n *V2ServerSession) Mac(message []byte, sequenceNumber int) ([]byte, error) { 121 | mac := NtlmV2Mac(message, sequenceNumber, n.serverHandle, n.ServerSealingKey, n.ServerSigningKey, n.NegotiateFlags) 122 | return mac, nil 123 | } 124 | 125 | func (n *V2ServerSession) VerifyMac(message, expectedMac []byte, sequenceNumber int) (bool, error) { 126 | mac := NtlmV2Mac(message, sequenceNumber, n.clientHandle, n.ClientSealingKey, n.ClientSigningKey, n.NegotiateFlags) 127 | return MacsEqual(mac, expectedMac), nil 128 | } 129 | 130 | func (n *V2ClientSession) Mac(message []byte, sequenceNumber int) ([]byte, error) { 131 | mac := NtlmV2Mac(message, sequenceNumber, n.clientHandle, n.ClientSealingKey, n.ClientSigningKey, n.NegotiateFlags) 132 | return mac, nil 133 | } 134 | 135 | func (n *V2ClientSession) VerifyMac(message, expectedMac []byte, sequenceNumber int) (bool, error) { 136 | mac := NtlmV2Mac(message, sequenceNumber, n.serverHandle, n.ServerSealingKey, n.ServerSigningKey, n.NegotiateFlags) 137 | return MacsEqual(mac, expectedMac), nil 138 | } 139 | 140 | /************** 141 | Server Session 142 | **************/ 143 | 144 | type V2ServerSession struct { 145 | V2Session 146 | } 147 | 148 | func (n *V2ServerSession) SetServerChallenge(challenge []byte) { 149 | n.serverChallenge = challenge 150 | } 151 | 152 | func (n *V2ServerSession) ProcessNegotiateMessage(nm *NegotiateMessage) (err error) { 153 | n.negotiateMessage = nm 154 | return 155 | } 156 | 157 | func (n *V2ServerSession) GenerateChallengeMessage() (cm *ChallengeMessage, err error) { 158 | cm = new(ChallengeMessage) 159 | cm.Signature = []byte("NTLMSSP\x00") 160 | cm.MessageType = uint32(2) 161 | cm.TargetName, _ = CreateBytePayload(make([]byte, 0)) 162 | 163 | flags := uint32(0) 164 | flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) 165 | flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags) 166 | flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) 167 | flags = NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags) 168 | flags = NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags) 169 | flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) 170 | flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags) 171 | flags = NTLMSSP_NEGOTIATE_DATAGRAM.Set(flags) 172 | flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags) 173 | flags = NTLMSSP_REQUEST_TARGET.Set(flags) 174 | flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags) 175 | flags = NTLMSSP_NEGOTIATE_128.Set(flags) 176 | 177 | cm.NegotiateFlags = flags 178 | 179 | n.serverChallenge = randomBytes(8) 180 | cm.ServerChallenge = n.serverChallenge 181 | cm.Reserved = make([]byte, 8) 182 | 183 | // Create the AvPairs we need 184 | pairs := new(AvPairs) 185 | pairs.AddAvPair(MsvAvNbDomainName, utf16FromString("REUTERS")) 186 | pairs.AddAvPair(MsvAvNbComputerName, utf16FromString("UKBP-CBTRMFE06")) 187 | pairs.AddAvPair(MsvAvDnsDomainName, utf16FromString("Reuters.net")) 188 | pairs.AddAvPair(MsvAvDnsComputerName, utf16FromString("ukbp-cbtrmfe06.Reuters.net")) 189 | pairs.AddAvPair(MsvAvDnsTreeName, utf16FromString("Reuters.net")) 190 | pairs.AddAvPair(MsvAvEOL, make([]byte, 0)) 191 | cm.TargetInfo = pairs 192 | cm.TargetInfoPayloadStruct, _ = CreateBytePayload(pairs.Bytes()) 193 | 194 | cm.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} 195 | return cm, nil 196 | } 197 | 198 | func (n *V2ServerSession) ProcessAuthenticateMessage(am *AuthenticateMessage) (err error) { 199 | n.authenticateMessage = am 200 | n.NegotiateFlags = am.NegotiateFlags 201 | n.clientChallenge = am.ClientChallenge() 202 | n.encryptedRandomSessionKey = am.EncryptedRandomSessionKey.Payload 203 | // Ignore the values used in SetUserInfo and use these instead from the authenticate message 204 | // They should always be correct (I hope) 205 | n.user = am.UserName.String() 206 | n.userDomain = am.DomainName.String() 207 | log.Printf("(ProcessAuthenticateMessage)NTLM v2 User %s Domain %s", n.user, n.userDomain) 208 | 209 | err = n.fetchResponseKeys() 210 | if err != nil { 211 | return err 212 | } 213 | 214 | timestamp := am.NtlmV2Response.NtlmV2ClientChallenge.TimeStamp 215 | avPairsBytes := am.NtlmV2Response.NtlmV2ClientChallenge.AvPairs.Bytes() 216 | 217 | err = n.computeExpectedResponses(timestamp, avPairsBytes) 218 | if err != nil { 219 | return err 220 | } 221 | 222 | if !bytes.Equal(am.NtChallengeResponseFields.Payload, n.ntChallengeResponse) { 223 | if !bytes.Equal(am.LmChallengeResponse.Payload, n.lmChallengeResponse) { 224 | return errors.New("Could not authenticate") 225 | } 226 | } 227 | 228 | err = n.computeKeyExchangeKey() 229 | if err != nil { 230 | return err 231 | } 232 | 233 | n.mic = am.Mic 234 | am.Mic = zeroBytes(16) 235 | 236 | err = n.computeExportedSessionKey() 237 | if err != nil { 238 | return err 239 | } 240 | 241 | if am.Version == nil { 242 | //UGH not entirely sure how this could possibly happen, going to put this in for now 243 | //TODO investigate if this ever is really happening 244 | am.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} 245 | 246 | log.Printf("Nil version in ntlmv2") 247 | } 248 | 249 | err = n.calculateKeys(am.Version.NTLMRevisionCurrent) 250 | if err != nil { 251 | return err 252 | } 253 | 254 | n.clientHandle, err = rc4Init(n.ClientSealingKey) 255 | if err != nil { 256 | return err 257 | } 258 | n.serverHandle, err = rc4Init(n.ServerSealingKey) 259 | if err != nil { 260 | return err 261 | } 262 | 263 | return nil 264 | } 265 | 266 | func (n *V2ServerSession) computeExportedSessionKey() (err error) { 267 | if NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.NegotiateFlags) { 268 | n.exportedSessionKey, err = rc4K(n.keyExchangeKey, n.encryptedRandomSessionKey) 269 | if err != nil { 270 | return err 271 | } 272 | // TODO: Calculate mic correctly. This calculation is not producing the right results now 273 | // n.calculatedMic = HmacMd5(n.exportedSessionKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes)) 274 | } else { 275 | n.exportedSessionKey = n.keyExchangeKey 276 | // TODO: Calculate mic correctly. This calculation is not producing the right results now 277 | // n.calculatedMic = HmacMd5(n.keyExchangeKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes)) 278 | } 279 | return nil 280 | } 281 | 282 | /************* 283 | Client Session 284 | **************/ 285 | 286 | type V2ClientSession struct { 287 | V2Session 288 | } 289 | 290 | func (n *V2ClientSession) GenerateNegotiateMessage() (nm *NegotiateMessage, err error) { 291 | return nil, nil 292 | } 293 | 294 | func (n *V2ClientSession) ProcessChallengeMessage(cm *ChallengeMessage) (err error) { 295 | n.challengeMessage = cm 296 | n.serverChallenge = cm.ServerChallenge 297 | n.clientChallenge = randomBytes(8) 298 | 299 | // Set up the default flags for processing the response. These are the flags that we will return 300 | // in the authenticate message 301 | flags := uint32(0) 302 | flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) 303 | flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags) 304 | flags = NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) 305 | flags = NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags) 306 | flags = NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags) 307 | flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) 308 | flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags) 309 | flags = NTLMSSP_NEGOTIATE_DATAGRAM.Set(flags) 310 | flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags) 311 | flags = NTLMSSP_REQUEST_TARGET.Set(flags) 312 | flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags) 313 | flags = NTLMSSP_NEGOTIATE_128.Set(flags) 314 | 315 | n.NegotiateFlags = flags 316 | 317 | err = n.fetchResponseKeys() 318 | if err != nil { 319 | return err 320 | } 321 | 322 | timestamp := timeToWindowsFileTime(time.Now()) 323 | err = n.computeExpectedResponses(timestamp, cm.TargetInfoPayloadStruct.Payload) 324 | if err != nil { 325 | return err 326 | } 327 | 328 | err = n.computeKeyExchangeKey() 329 | if err != nil { 330 | return err 331 | } 332 | 333 | err = n.computeEncryptedSessionKey() 334 | if err != nil { 335 | return err 336 | } 337 | 338 | err = n.calculateKeys(cm.Version.NTLMRevisionCurrent) 339 | if err != nil { 340 | return err 341 | } 342 | 343 | n.clientHandle, err = rc4Init(n.ClientSealingKey) 344 | if err != nil { 345 | return err 346 | } 347 | n.serverHandle, err = rc4Init(n.ServerSealingKey) 348 | if err != nil { 349 | return err 350 | } 351 | return nil 352 | } 353 | 354 | func (n *V2ClientSession) GenerateAuthenticateMessage() (am *AuthenticateMessage, err error) { 355 | am = new(AuthenticateMessage) 356 | am.Signature = []byte("NTLMSSP\x00") 357 | am.MessageType = uint32(3) 358 | am.LmChallengeResponse, _ = CreateBytePayload(n.lmChallengeResponse) 359 | am.NtChallengeResponseFields, _ = CreateBytePayload(n.ntChallengeResponse) 360 | am.DomainName, _ = CreateStringPayload(n.userDomain) 361 | am.UserName, _ = CreateStringPayload(n.user) 362 | am.Workstation, _ = CreateStringPayload("SQUAREMILL") 363 | am.EncryptedRandomSessionKey, _ = CreateBytePayload(n.encryptedRandomSessionKey) 364 | am.NegotiateFlags = n.NegotiateFlags 365 | am.Mic = make([]byte, 16) 366 | am.Version = &VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: 0x0F} 367 | return am, nil 368 | } 369 | 370 | func (n *V2ClientSession) computeEncryptedSessionKey() (err error) { 371 | if NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.NegotiateFlags) { 372 | n.exportedSessionKey = randomBytes(16) 373 | n.encryptedRandomSessionKey, err = rc4K(n.keyExchangeKey, n.exportedSessionKey) 374 | if err != nil { 375 | return err 376 | } 377 | } else { 378 | n.encryptedRandomSessionKey = n.keyExchangeKey 379 | } 380 | return nil 381 | } 382 | 383 | /******************************** 384 | NTLM V2 Password hash functions 385 | *********************************/ 386 | 387 | // Define ntowfv2(Passwd, User, UserDom) as 388 | func ntowfv2(user string, passwd string, userDom string) []byte { 389 | concat := utf16FromString(strings.ToUpper(user) + userDom) 390 | return hmacMd5(md4(utf16FromString(passwd)), concat) 391 | } 392 | 393 | // Define lmowfv2(Passwd, User, UserDom) as 394 | func lmowfv2(user string, passwd string, userDom string) []byte { 395 | return ntowfv2(user, passwd, userDom) 396 | } 397 | 398 | /******************************** 399 | Helper functions 400 | *********************************/ 401 | 402 | func timeToWindowsFileTime(t time.Time) []byte { 403 | var ll int64 404 | ll = (int64(t.Unix()) * int64(10000000)) + int64(116444736000000000) 405 | buffer := bytes.NewBuffer(make([]byte, 0, 8)) 406 | binary.Write(buffer, binary.LittleEndian, ll) 407 | return buffer.Bytes() 408 | } 409 | --------------------------------------------------------------------------------