initial commit

This commit is contained in:
Conor Hunt 2012-11-09 15:39:05 -05:00
commit cb96a1fb85
29 changed files with 3109 additions and 0 deletions

9
README Normal file
View File

@ -0,0 +1,9 @@
= NTLM Implementation for Go
== Getting started
# Set your GOPATH
export GOPATH=`pwd`
# Retrieve all remote packages
go get all

133
src/ntlm/crypto.go Normal file
View File

@ -0,0 +1,133 @@
package ntlm
import (
desP "crypto/des"
hmacP "crypto/hmac"
md5P "crypto/md5"
"crypto/rand"
rc4P "crypto/rc4"
crc32P "hash/crc32"
md4P "ntlm/md4"
)
func md4(data []byte) []byte {
md4 := md4P.New()
md4.Write(data)
return md4.Sum(nil)
}
func md5(data []byte) []byte {
md5 := md5P.New()
md5.Write(data)
return md5.Sum(nil)
}
// Indicates the computation of a 16-byte HMAC-keyed MD5 message digest of the byte string M using the key K.
func hmacMd5(key []byte, data []byte) []byte {
mac := hmacP.New(md5P.New, key)
mac.Write(data)
return mac.Sum(nil)
}
// Indicates the computation of an N-byte cryptographic- strength random number.
func nonce(length int) []byte {
result := make([]byte, length)
rand.Read(result)
return result
}
func crc32(bytes []byte) uint32 {
crc := crc32P.New(crc32P.IEEETable)
crc.Write(bytes)
return crc.Sum32()
}
// Indicates the encryption of data item D with the key K using the RC4 algorithm.
func rc4K(key []byte, ciphertext []byte) ([]byte, error) {
cipher, err := rc4P.NewCipher(key)
if err != nil {
return nil, err
}
result := make([]byte, len(ciphertext))
cipher.XORKeyStream(result, ciphertext)
return result, nil
}
func rc4Init(key []byte) (cipher *rc4P.Cipher, err error) {
cipher, err = rc4P.NewCipher(key)
if err != nil {
return nil, err
}
return cipher, nil
}
func rc4(cipher *rc4P.Cipher, ciphertext []byte) []byte {
result := make([]byte, len(ciphertext))
cipher.XORKeyStream(result, ciphertext)
return result
}
// Indicates the encryption of an 8-byte data item D with the 7-byte key K using the Data Encryption Standard (DES)
// algorithm in Electronic Codebook (ECB) mode. The result is 8 bytes in length ([FIPS46-2]).
func des(key []byte, ciphertext []byte) ([]byte, error) {
calcKey := createDesKey(key)
cipher, err := desP.NewCipher(calcKey)
if err != nil {
return nil, err
}
result := make([]byte, len(ciphertext))
cipher.Encrypt(result, ciphertext)
return result, nil
}
// Indicates the encryption of an 8-byte data item D with the 16-byte key K using the Data Encryption Standard Long (DESL) algorithm.
// The result is 24 bytes in length. DESL(K, D) is computed as follows.
// Note K[] implies a key represented as a character array.
func desL(key []byte, cipherText []byte) ([]byte, error) {
out1, err := des(zeroPaddedBytes(key, 0, 7), cipherText)
if err != nil {
return nil, err
}
out2, err := des(zeroPaddedBytes(key, 7, 7), cipherText)
if err != nil {
return nil, err
}
out3, err := des(zeroPaddedBytes(key, 14, 7), cipherText)
if err != nil {
return nil, err
}
return concat(out1, out2, out3), nil
}
// Creates a DES encryption key from the given 7 byte key material.
func createDesKey(keyBytes []byte) []byte {
material := zeroBytes(8)
material[0] = keyBytes[0]
material[1] = (byte)(keyBytes[0]<<7 | (keyBytes[1]&0xff)>>1)
material[2] = (byte)(keyBytes[1]<<6 | (keyBytes[2]&0xff)>>2)
material[3] = (byte)(keyBytes[2]<<5 | (keyBytes[3]&0xff)>>3)
material[4] = (byte)(keyBytes[3]<<4 | (keyBytes[4]&0xff)>>4)
material[5] = (byte)(keyBytes[4]<<3 | (keyBytes[5]&0xff)>>5)
material[6] = (byte)(keyBytes[5]<<2 | (keyBytes[6]&0xff)>>6)
material[7] = (byte)(keyBytes[6] << 1)
oddParity(material)
return material
}
// Applies odd parity to the given byte array.
func oddParity(bytes []byte) {
for i := 0; i < len(bytes); i++ {
b := bytes[i]
needsParity := (((b >> 7) ^ (b >> 6) ^ (b >> 5) ^ (b >> 4) ^ (b >> 3) ^ (b >> 2) ^ (b >> 1)) & 0x01) == 0
if needsParity {
bytes[i] = bytes[i] | byte(0x01)
} else {
bytes[i] = bytes[i] & byte(0xfe)
}
}
}

62
src/ntlm/crypto_test.go Normal file
View File

@ -0,0 +1,62 @@
package ntlm
import (
"bytes"
"encoding/hex"
"testing"
)
func TestMd4(t *testing.T) {
data := []byte{1, 2, 3, 4, 5}
byteData, _ := hex.DecodeString("93ebafdfedd1994e8018cc295cc1a8ee")
if !bytes.Equal(md4(data), byteData) {
t.Error("MD4 result not correct")
}
}
func TestHmacMd5(t *testing.T) {
data := []byte{1, 2, 3, 4, 5}
byteData, _ := hex.DecodeString("9155578efbf3810a2adb4dee232a5fee")
if !bytes.Equal(hmacMd5(data, data), byteData) {
t.Error("HmacMd5 result not correct")
}
}
func TestNonce(t *testing.T) {
data := nonce(10)
if len(data) != 10 {
t.Error("Nonce is incorrect length")
}
}
func TestRc4K(t *testing.T) {
data := []byte{1, 2, 3, 4, 5}
key := []byte{1, 2, 3, 4, 5}
result, err := rc4K(key, data)
if err != nil {
// TODO: Need some sample data to test RC4K
// t.Error("Error returned for RC4K")
}
if !bytes.Equal(result, data) {
// t.Error("RC4K result not correct")
}
}
func TestDesL(t *testing.T) {
key, _ := hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d")
message := []byte("12345678")
result, _ := desL(key, message)
expected, _ := hex.DecodeString("1192855D461A9754D189D8AE94D82488E3707C0662C0476A")
if !bytes.Equal(result, expected) {
t.Errorf("DesL did not produce correct result, got %s expected %s", hex.EncodeToString(result), hex.EncodeToString(expected))
}
}
func TestCRC32(t *testing.T) {
bytes := []byte("Discard medicine more than two years old.")
result := crc32(bytes)
expected := uint32(0x6b9cdfe7)
if expected != result {
t.Errorf("CRC 32 data is not correct got %d expected %d", result, expected)
}
}

48
src/ntlm/helpers.go Normal file
View File

@ -0,0 +1,48 @@
package ntlm
import (
"bytes"
"crypto/rand"
"unicode/utf16"
)
// Concatenate two byte slices into a new slice
func concat(ar ...[]byte) []byte {
return bytes.Join(ar, nil)
}
// Create a 0 initialized slice of bytes
func zeroBytes(length int) []byte {
return make([]byte, length, length)
}
func randomBytes(length int) []byte {
randombytes := make([]byte, length)
_, err := rand.Read(randombytes)
if err != nil {
} // TODO: What to do with err here
return randombytes
}
// Zero pad the input byte slice to the given size
// bytes - input byte slice
// offset - where to start taking the bytes from the input slice
// size - size of the output byte slize
func zeroPaddedBytes(bytes []byte, offset int, size int) []byte {
newSlice := zeroBytes(size)
for i := 0; i < size && i+offset < len(bytes); i++ {
newSlice[i] = bytes[i+offset]
}
return newSlice
}
func utf16FromString(s string) []byte {
encoded := utf16.Encode([]rune(s))
// TODO: I'm sure there is an easier way to do the conversion from utf16 to bytes
result := zeroBytes(len(encoded) * 2)
for i := 0; i < len(encoded); i++ {
result[i*2] = byte(encoded[i])
result[i*2+1] = byte(encoded[i] << 8)
}
return result
}

15
src/ntlm/helpers_test.go Normal file
View File

@ -0,0 +1,15 @@
package ntlm
import (
"bytes"
"encoding/hex"
"testing"
)
func TestUTf16ToString(t *testing.T) {
expected, _ := hex.DecodeString("5500730065007200")
result := utf16FromString("User")
if !bytes.Equal(expected, result) {
t.Errorf("UTF16ToString failed got %s expected %s", hex.EncodeToString(result), "5500730065007200")
}
}

72
src/ntlm/keys.go Normal file
View File

@ -0,0 +1,72 @@
package ntlm
import (
"ntlm/messages"
)
// Define KXKEY(SessionBaseKey, LmChallengeResponse, ServerChallenge) as
func kxKey(flags uint32, sessionBaseKey []byte, lmChallengeResponse []byte, serverChallenge []byte, lmnowf []byte) (keyExchangeKey []byte, err error) {
if messages.NTLMSSP_NEGOTIATE_LM_KEY.IsSet(flags) {
var part1, part2 []byte
part1, err = des(lmnowf[0:7], lmChallengeResponse[0:8])
if err != nil {
return nil, err
}
key := append([]byte{lmnowf[7]}, []byte{0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD}...)
part2, err = des(key, lmChallengeResponse[0:8])
if err != nil {
return nil, err
}
keyExchangeKey = concat(part1, part2)
} else if messages.NTLMSSP_REQUEST_NON_NT_SESSION_KEY.IsSet(flags) {
keyExchangeKey = concat(lmnowf[0:8], zeroBytes(8))
} else {
keyExchangeKey = sessionBaseKey
}
return
}
// Define SIGNKEY(NegFlg, RandomSessionKey, Mode) as
func signKey(flags uint32, randomSessionKey []byte, mode string) (signKey []byte) {
if messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(flags) {
if mode == "Client" {
signKey = md5(concat(randomSessionKey, []byte("session key to client-to-server signing key magic constant\x00")))
} else {
signKey = md5(concat(randomSessionKey, []byte("session key to server-to-client signing key magic constant\x00")))
}
} else {
signKey = nil
}
return
}
// Define SEALKEY(NegotiateFlags, RandomSessionKey, Mode) as
func sealKey(flags uint32, randomSessionKey []byte, mode string) (sealKey []byte) {
if messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(flags) {
if messages.NTLMSSP_NEGOTIATE_128.IsSet(flags) {
sealKey = randomSessionKey
} else if messages.NTLMSSP_NEGOTIATE_56.IsSet(flags) {
sealKey = randomSessionKey[0:7]
} else {
sealKey = randomSessionKey[0:5]
}
if mode == "Client" {
sealKey = md5(concat(sealKey, []byte("session key to client-to-server sealing key magic constant\x00")))
} else {
sealKey = md5(concat(sealKey, []byte("session key to server-to-client sealing key magic constant\x00")))
}
} else if messages.NTLMSSP_NEGOTIATE_LM_KEY.IsSet(flags) {
if messages.NTLMSSP_NEGOTIATE_56.IsSet(flags) {
sealKey = concat(randomSessionKey[0:7], []byte{0xA0})
} else {
sealKey = concat(randomSessionKey[0:4], []byte{0xE5, 0x38, 0xB0})
}
} else {
sealKey = randomSessionKey
}
return
}

27
src/ntlm/md4/LICENSE Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

118
src/ntlm/md4/md4.go Normal file
View File

@ -0,0 +1,118 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package md4 implements the MD4 hash algorithm as defined in RFC 1320.
package md4
import (
"crypto"
"hash"
)
func init() {
crypto.RegisterHash(crypto.MD4, New)
}
// The size of an MD4 checksum in bytes.
const Size = 16
// The blocksize of MD4 in bytes.
const BlockSize = 64
const (
_Chunk = 64
_Init0 = 0x67452301
_Init1 = 0xEFCDAB89
_Init2 = 0x98BADCFE
_Init3 = 0x10325476
)
// digest represents the partial evaluation of a checksum.
type digest struct {
s [4]uint32
x [_Chunk]byte
nx int
len uint64
}
func (d *digest) Reset() {
d.s[0] = _Init0
d.s[1] = _Init1
d.s[2] = _Init2
d.s[3] = _Init3
d.nx = 0
d.len = 0
}
// New returns a new hash.Hash computing the MD4 checksum.
func New() hash.Hash {
d := new(digest)
d.Reset()
return d
}
func (d *digest) Size() int { return Size }
func (d *digest) BlockSize() int { return BlockSize }
func (d *digest) Write(p []byte) (nn int, err error) {
nn = len(p)
d.len += uint64(nn)
if d.nx > 0 {
n := len(p)
if n > _Chunk-d.nx {
n = _Chunk - d.nx
}
for i := 0; i < n; i++ {
d.x[d.nx+i] = p[i]
}
d.nx += n
if d.nx == _Chunk {
_Block(d, d.x[0:])
d.nx = 0
}
p = p[n:]
}
n := _Block(d, p)
p = p[n:]
if len(p) > 0 {
d.nx = copy(d.x[:], p)
}
return
}
func (d0 *digest) Sum(in []byte) []byte {
// Make a copy of d0, so that caller can keep writing and summing.
d := new(digest)
*d = *d0
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
len := d.len
var tmp [64]byte
tmp[0] = 0x80
if len%64 < 56 {
d.Write(tmp[0 : 56-len%64])
} else {
d.Write(tmp[0 : 64+56-len%64])
}
// Length in bits.
len <<= 3
for i := uint(0); i < 8; i++ {
tmp[i] = byte(len >> (8 * i))
}
d.Write(tmp[0:8])
if d.nx != 0 {
panic("d.nx != 0")
}
for _, s := range d.s {
in = append(in, byte(s>>0))
in = append(in, byte(s>>8))
in = append(in, byte(s>>16))
in = append(in, byte(s>>24))
}
return in
}

71
src/ntlm/md4/md4_test.go Normal file
View File

@ -0,0 +1,71 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package md4
import (
"fmt"
"io"
"testing"
)
type md4Test struct {
out string
in string
}
var golden = []md4Test{
{"31d6cfe0d16ae931b73c59d7e0c089c0", ""},
{"bde52cb31de33e46245e05fbdbd6fb24", "a"},
{"ec388dd78999dfc7cf4632465693b6bf", "ab"},
{"a448017aaf21d8525fc10ae87aa6729d", "abc"},
{"41decd8f579255c5200f86a4bb3ba740", "abcd"},
{"9803f4a34e8eb14f96adba49064a0c41", "abcde"},
{"804e7f1c2586e50b49ac65db5b645131", "abcdef"},
{"752f4adfe53d1da0241b5bc216d098fc", "abcdefg"},
{"ad9daf8d49d81988590a6f0e745d15dd", "abcdefgh"},
{"1e4e28b05464316b56402b3815ed2dfd", "abcdefghi"},
{"dc959c6f5d6f9e04e4380777cc964b3d", "abcdefghij"},
{"1b5701e265778898ef7de5623bbe7cc0", "Discard medicine more than two years old."},
{"d7f087e090fe7ad4a01cb59dacc9a572", "He who has a shady past knows that nice guys finish last."},
{"a6f8fd6df617c72837592fc3570595c9", "I wouldn't marry him with a ten foot pole."},
{"c92a84a9526da8abc240c05d6b1a1ce0", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"},
{"f6013160c4dcb00847069fee3bb09803", "The days of the digital watch are numbered. -Tom Stoppard"},
{"2c3bb64f50b9107ed57640fe94bec09f", "Nepal premier won't resign."},
{"45b7d8a32c7806f2f7f897332774d6e4", "For every action there is an equal and opposite government program."},
{"b5b4f9026b175c62d7654bdc3a1cd438", "His money is twice tainted: 'taint yours and 'taint mine."},
{"caf44e80f2c20ce19b5ba1cab766e7bd", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"},
{"191fae6707f496aa54a6bce9f2ecf74d", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"},
{"9ddc753e7a4ccee6081cd1b45b23a834", "size: a.out: bad magic"},
{"8d050f55b1cadb9323474564be08a521", "The major problem is with sendmail. -Mark Horton"},
{"ad6e2587f74c3e3cc19146f6127fa2e3", "Give me a rock, paper and scissors and I will move the world. CCFestoon"},
{"1d616d60a5fabe85589c3f1566ca7fca", "If the enemy is within range, then so are you."},
{"aec3326a4f496a2ced65a1963f84577f", "It's well we cannot hear the screams/That we create in others' dreams."},
{"77b4fd762d6b9245e61c50bf6ebf118b", "You remind me of a TV show, but that's all right: I watch it anyway."},
{"e8f48c726bae5e516f6ddb1a4fe62438", "C is as portable as Stonehedge!!"},
{"a3a84366e7219e887423b01f9be7166e", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"},
{"a6b7aa35157e984ef5d9b7f32e5fbb52", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"},
{"75661f0545955f8f9abeeb17845f3fd6", "How can you write a big system without C++? -Paul Glick"},
}
func TestGolden(t *testing.T) {
for i := 0; i < len(golden); i++ {
g := golden[i]
c := New()
for j := 0; j < 3; j++ {
if j < 2 {
io.WriteString(c, g.in)
} else {
io.WriteString(c, g.in[0:len(g.in)/2])
c.Sum(nil)
io.WriteString(c, g.in[len(g.in)/2:])
}
s := fmt.Sprintf("%x", c.Sum(nil))
if s != g.out {
t.Fatalf("md4[%d](%s) = %s want %s", j, g.in, s, g.out)
}
c.Reset()
}
}
}

89
src/ntlm/md4/md4block.go Normal file
View File

@ -0,0 +1,89 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// MD4 block step.
// In its own file so that a faster assembly or C version
// can be substituted easily.
package md4
var shift1 = []uint{3, 7, 11, 19}
var shift2 = []uint{3, 5, 9, 13}
var shift3 = []uint{3, 9, 11, 15}
var xIndex2 = []uint{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}
var xIndex3 = []uint{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
func _Block(dig *digest, p []byte) int {
a := dig.s[0]
b := dig.s[1]
c := dig.s[2]
d := dig.s[3]
n := 0
var X [16]uint32
for len(p) >= _Chunk {
aa, bb, cc, dd := a, b, c, d
j := 0
for i := 0; i < 16; i++ {
X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
j += 4
}
// If this needs to be made faster in the future,
// the usual trick is to unroll each of these
// loops by a factor of 4; that lets you replace
// the shift[] lookups with constants and,
// with suitable variable renaming in each
// unrolled body, delete the a, b, c, d = d, a, b, c
// (or you can let the optimizer do the renaming).
//
// The index variables are uint so that % by a power
// of two can be optimized easily by a compiler.
// Round 1.
for i := uint(0); i < 16; i++ {
x := i
s := shift1[i%4]
f := ((c ^ d) & b) ^ d
a += f + X[x]
a = a<<s | a>>(32-s)
a, b, c, d = d, a, b, c
}
// Round 2.
for i := uint(0); i < 16; i++ {
x := xIndex2[i]
s := shift2[i%4]
g := (b & c) | (b & d) | (c & d)
a += g + X[x] + 0x5a827999
a = a<<s | a>>(32-s)
a, b, c, d = d, a, b, c
}
// Round 3.
for i := uint(0); i < 16; i++ {
x := xIndex3[i]
s := shift3[i%4]
h := b ^ c ^ d
a += h + X[x] + 0x6ed9eba1
a = a<<s | a>>(32-s)
a, b, c, d = d, a, b, c
}
a += aa
b += bb
c += cc
d += dd
p = p[_Chunk:]
n += _Chunk
}
dig.s[0] = a
dig.s[1] = b
dig.s[2] = c
dig.s[3] = d
return n
}

View File

@ -0,0 +1,280 @@
package messages
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
)
type Authenticate struct {
// sig - 8 bytes
Signature []byte
// message type - 4 bytes
MessageType uint32
// The LmChallenge Response can be v1 or v2
LmChallengeResponse *PayloadStruct // 8 bytes
LmV1Response *LmV1Response
LmV2Response *LmV2Response
// The NtChallengeResponse can be v1 or v2
NtChallengeResponseFields *PayloadStruct // 8 bytes
NtlmV1Response *NtlmV1Response
NtlmV2Response *NtlmV2Response
DomainName *PayloadStruct // 8 bytes
UserName *PayloadStruct // 8 bytes
Workstation *PayloadStruct // 8 bytes
// If the NTLMSSP_NEGOTIATE_KEY_EXCH flag is set in the neogitate flags then this will point to the offset in the payload
// with the key, otherwise it will have Len = 0. According to Davenport these bytes are optional (see Type3 message).
// The MS-NLMP docs do not mention this.
EncryptedRandomSessionKey *PayloadStruct // 8 bytes
/// 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
// conclusion of negotiation—the choices the client has made from the options the server offered in the CHALLENGE_MESSAGE.
// In connection-oriented mode, a NEGOTIATE structure that contains the set of bit flags (section 2.2.2.5) negotiated in
// the previous messages.
NegotiateFlags uint32 // 4 bytes
// 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>
Version *VersionStruct
// The message integrity for the NTLM NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE, and AUTHENTICATE_MESSAGE.<10>
Mic []byte // 16 bytes
// payload - variable
Payload []byte
}
func ParseAuthenticateMessage(body []byte, ntlmVersion int) (*Authenticate, error) {
am := new(Authenticate)
am.Signature = body[0:8]
if !bytes.Equal(am.Signature, []byte("NTLMSSP\x00")) {
return nil, errors.New("Invalid NTLM message signature")
}
am.MessageType = binary.LittleEndian.Uint32(body[8:12])
if am.MessageType != 3 {
return nil, errors.New("Invalid NTLM message type should be 0x00000003 for authenticate message")
}
var err error
am.LmChallengeResponse, err = ReadBytePayload(12, body)
if err != nil {
return nil, err
}
if ntlmVersion == 2 {
am.LmV2Response = ReadLmV2Response(am.LmChallengeResponse.Payload)
} else {
am.LmV1Response = ReadLmV1Response(am.LmChallengeResponse.Payload)
}
am.NtChallengeResponseFields, err = ReadBytePayload(20, body)
if err != nil {
return nil, err
}
// Check to see if this is a v1 or v2 response
if ntlmVersion == 2 {
am.NtlmV2Response = ReadNtlmV2Response(am.NtChallengeResponseFields.Payload)
} else {
am.NtlmV1Response = ReadNtlmV1Response(am.NtChallengeResponseFields.Payload)
}
am.DomainName, err = ReadStringPayload(28, body)
if err != nil {
return nil, err
}
am.UserName, err = ReadStringPayload(36, body)
if err != nil {
return nil, err
}
am.Workstation, err = ReadStringPayload(44, body)
if err != nil {
return nil, err
}
lowestOffset := am.getLowestPayloadOffset()
offset := 52
// If the lowest payload offset is 52 then:
// The Session Key, flags, and OS Version structure are omitted. The data (payload) block in this case starts after the Workstation Name
// security buffer header, at offset 52. This form is seen in older Win9x-based systems. This is from the davenport notes about Type 3
// messages and this information does not seem to be present in the MS-NLMP document
if lowestOffset > 52 {
am.EncryptedRandomSessionKey, err = ReadBytePayload(offset, body)
if err != nil {
return nil, err
}
offset = offset + 8
am.NegotiateFlags = binary.LittleEndian.Uint32(body[offset : offset+4])
offset = offset + 4
// 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>
if NTLMSSP_NEGOTIATE_VERSION.IsSet(am.NegotiateFlags) {
am.Version, err = ReadVersionStruct(body[offset : offset+8])
if err != nil {
return nil, err
}
offset = offset + 8
}
// The MS-NLMP has this to say about the MIC
// "An AUTHENTICATE_MESSAGE indicates the presence of a MIC field if the TargetInfo field has an AV_PAIR structure whose two fields are:
// AvId == MsvAvFlags Value bit 0x2 == 1"
// However there is no TargetInfo structure in the Authenticate Message! There is one in the Challenge Message though. So I'm using
// 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
// there is a MIC and read it out.
var lowestOffset = am.getLowestPayloadOffset()
if lowestOffset > offset {
// MIC - 16 bytes
am.Mic = body[offset : offset+16]
offset = offset + 16
}
}
am.Payload = body[offset:]
return am, nil
}
func (a *Authenticate) ClientChallenge() (response []byte) {
if a.NtlmV2Response != nil {
response = a.NtlmV2Response.NtlmV2ClientChallenge.ChallengeFromClient
} else if a.NtlmV1Response != nil && NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(a.NegotiateFlags) {
response = a.LmV1Response.Response[0:8]
}
return response
}
func (a *Authenticate) getLowestPayloadOffset() int {
payloadStructs := [...]*PayloadStruct{a.LmChallengeResponse, a.NtChallengeResponseFields, a.DomainName, a.UserName, a.Workstation, a.EncryptedRandomSessionKey}
// Find the lowest offset value
lowest := 9999
for i := range payloadStructs {
p := payloadStructs[i]
if p != nil && p.Offset > 0 && int(p.Offset) < lowest {
lowest = int(p.Offset)
}
}
return lowest
}
func (a *Authenticate) Bytes() []byte {
payloadLen := int(a.LmChallengeResponse.Len + a.NtChallengeResponseFields.Len + a.DomainName.Len + a.UserName.Len + a.Workstation.Len + a.EncryptedRandomSessionKey.Len)
messageLen := 8 + 4 + 6*8 + 4 + 8 + 16
payloadOffset := uint32(messageLen)
messageBytes := make([]byte, 0, messageLen+payloadLen)
buffer := bytes.NewBuffer(messageBytes)
buffer.Write(a.Signature)
binary.Write(buffer, binary.LittleEndian, a.MessageType)
a.LmChallengeResponse.Offset = payloadOffset
payloadOffset += uint32(a.LmChallengeResponse.Len)
buffer.Write(a.LmChallengeResponse.Bytes())
a.NtChallengeResponseFields.Offset = payloadOffset
payloadOffset += uint32(a.NtChallengeResponseFields.Len)
buffer.Write(a.NtChallengeResponseFields.Bytes())
a.DomainName.Offset = payloadOffset
payloadOffset += uint32(a.DomainName.Len)
buffer.Write(a.DomainName.Bytes())
a.UserName.Offset = payloadOffset
payloadOffset += uint32(a.UserName.Len)
buffer.Write(a.UserName.Bytes())
a.Workstation.Offset = payloadOffset
payloadOffset += uint32(a.Workstation.Len)
buffer.Write(a.Workstation.Bytes())
a.EncryptedRandomSessionKey.Offset = payloadOffset
payloadOffset += uint32(a.EncryptedRandomSessionKey.Len)
buffer.Write(a.EncryptedRandomSessionKey.Bytes())
buffer.Write(Uint32ToBytes(a.NegotiateFlags))
buffer.Write(a.Version.Bytes())
if a.Mic != nil {
buffer.Write(a.Mic)
} else {
buffer.Write(make([]byte, 16))
}
// Write out the payloads
buffer.Write(a.LmChallengeResponse.Payload)
buffer.Write(a.NtChallengeResponseFields.Payload)
buffer.Write(a.DomainName.Payload)
buffer.Write(a.UserName.Payload)
buffer.Write(a.Workstation.Payload)
buffer.Write(a.EncryptedRandomSessionKey.Payload)
return buffer.Bytes()
}
func (a *Authenticate) String() string {
var buffer bytes.Buffer
buffer.WriteString("Authenticate NTLM Message\n")
buffer.WriteString(fmt.Sprintf("Payload Offset: %d Length: %d\n", a.getLowestPayloadOffset(), len(a.Payload)))
if a.LmV2Response != nil {
buffer.WriteString(a.LmV2Response.String())
buffer.WriteString("\n")
}
if a.LmV1Response != nil {
buffer.WriteString(a.LmV1Response.String())
buffer.WriteString("\n")
}
if a.NtlmV2Response != nil {
buffer.WriteString(a.NtlmV2Response.String())
buffer.WriteString("\n")
}
if a.NtlmV1Response != nil {
buffer.WriteString(fmt.Sprintf("NtlmResponse Length: %d\n", a.NtChallengeResponseFields.Len))
buffer.WriteString(a.NtlmV1Response.String())
buffer.WriteString("\n")
}
buffer.WriteString(fmt.Sprintf("UserName: %s\n", a.UserName.String()))
buffer.WriteString(fmt.Sprintf("DomainName: %s\n", a.DomainName.String()))
buffer.WriteString(fmt.Sprintf("Workstation: %s\n", a.Workstation.String()))
if a.EncryptedRandomSessionKey != nil {
buffer.WriteString(fmt.Sprintf("EncryptedRandomSessionKey: %s\n", a.EncryptedRandomSessionKey.String()))
}
if a.Version != nil {
buffer.WriteString(fmt.Sprintf("Version: %s\n", a.Version.String()))
}
if a.Mic != nil {
buffer.WriteString(fmt.Sprintf("MIC: %s\n", hex.EncodeToString(a.Mic)))
}
buffer.WriteString(fmt.Sprintf("Flags %d\n", a.NegotiateFlags))
buffer.WriteString(FlagsToString(a.NegotiateFlags))
return buffer.String()
}

View File

@ -0,0 +1,87 @@
package messages
import (
"bytes"
"encoding/base64"
"encoding/hex"
"testing"
)
func checkPayloadStruct(t *testing.T, payloadStruct *PayloadStruct, len uint16, offset uint32) {
if payloadStruct.Len != len || payloadStruct.Offset != offset {
t.Errorf("Failed to parse payload struct %d, %d", payloadStruct.Len, payloadStruct.Offset)
}
}
func TestAuthenticateNtlmV1(t *testing.T) {
authenticateMessage := "TlRMTVNTUAADAAAAGAAYAIgAAAAYABgAoAAAAAAAAABYAAAAIAAgAFgAAAAQABAAeAAAABAAEAC4AAAAVYKQYgYBsR0AAAAP2BgW++b14Dh6Z5B4Xs1DiHAAYQB1AGwAQABwAGEAdQBsAGQAaQB4AC4AbgBlAHQAVwBJAE4ANwBfAEkARQA4ACugxZFzvHB4P6LdKbbZpiYHo2ErZURLiSugxZFzvHB4P6LdKbbZpiYHo2ErZURLibmpCUlnbq2I4LAdEhLdg7I="
authenticateData, err := base64.StdEncoding.DecodeString(authenticateMessage)
if err != nil {
t.Error("Could not base64 decode message data")
}
a, err := ParseAuthenticateMessage(authenticateData, 1)
if err != nil {
t.Error("Could not parse authenticate message")
}
a.String()
outBytes := a.Bytes()
if len(outBytes) > 0 {
reparsed, err := ParseAuthenticateMessage(outBytes, 1)
if err != nil {
t.Error("Could not re-parse authenticate message")
}
if reparsed.String() != a.String() {
t.Error("Reparsed message is not the same")
}
} else {
t.Error("Invalid authenticate messsage bytes")
}
}
func TestAuthenticateNtlmV2(t *testing.T) {
authenticateMessage := "TlRMTVNTUAADAAAAGAAYAI4AAAAGAQYBpgAAAAAAAABYAAAAIAAgAFgAAAAWABYAeAAAABAAEACsAQAAVYKQQgYAchcAAAAPpdhi9ItaLWwSGpFMT4VQbnAAYQB1AGwAQABwAGEAdQBsAGQAaQB4AC4AbgBlAHQASQBQAC0AMABBADAAQwAzAEEAMQBFAAE/QEbbIB1InAX5KMgp4s4wmpPZ9jp9T3EC95rRY01DhMSv1kei5wYBAQAAAAAAADM6xfahoM0BMJqT2fY6fU8AAAAAAgAOAFIARQBVAFQARQBSAFMAAQAcAFUASwBCAFAALQBDAEIAVABSAE0ARgBFADAANgAEABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAMANAB1AGsAYgBwAC0AYwBiAHQAcgBtAGYAZQAwADYALgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQABQAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAAIADAAMAAAAAAAAAAAAAAAADAAAFaspfI82pMCKSuN2L09orn37EQVvxCSqVqQhCloFhQeAAAAAAAAAADRgm1iKYwwmIF3axms/dIe"
authenticateData, err := base64.StdEncoding.DecodeString(authenticateMessage)
if err != nil {
t.Error("Could not base64 decode message data")
}
a, err := ParseAuthenticateMessage(authenticateData, 2)
if err != nil || a == nil {
t.Error("Failed to parse authenticate message " + err.Error())
}
checkPayloadStruct(t, a.LmChallengeResponse, 24, 142)
checkPayloadStruct(t, a.NtChallengeResponseFields, 262, 166)
checkPayloadStruct(t, a.DomainName, 0, 88)
checkPayloadStruct(t, a.UserName, 32, 88)
checkPayloadStruct(t, a.Workstation, 22, 120)
checkPayloadStruct(t, a.EncryptedRandomSessionKey, 16, 428)
if a.NegotiateFlags != uint32(1116766805) {
t.Errorf("Authenticate negotiate flags not correct should be %d got %d", uint32(1116766805), a.NegotiateFlags)
}
mic, err := hex.DecodeString("a5d862f48b5a2d6c121a914c4f85506e")
if !bytes.Equal(a.Mic, mic) {
t.Errorf("Mic not correct, should be %s, got %s", "a5d862f48b5a2d6c121a914c4f85506e", hex.EncodeToString(a.Mic))
}
if len(a.Payload) != 356 {
t.Errorf("Length of payload is incorrect got: %d, should be %d", len(a.Payload), 356)
}
a.String()
// Generate the bytes from the message and reparse it and make sure that works
bytes := a.Bytes()
if len(bytes) == 0 {
}
}

View File

@ -0,0 +1,165 @@
package messages
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"reflect"
)
type AvPairType uint16
// MS-NLMP - 2.2.2.1 AV_PAIR
const (
// 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.
MsvAvEOL AvPairType = iota
// 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.
MsvAvNbComputerName
// 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.
MsvAvNbDomainName
// The fully qualified domain name (FQDN (1)) of the computer. The name MUST be in Unicode, and is not null-terminated.
MsvAvDnsComputerName
// The FQDN (2) of the domain. The name MUST be in Unicode, and is not null-terminate.
MsvAvDnsDomainName
// The FQDN (2) of the forest. The name MUST be in Unicode, and is not null-terminated.<11>
MsvAvDnsTreeName
// A 32-bit value indicating server or client configuration.
// 0x00000001: indicates to the client that the account authentication is constrained.
// 0x00000002: indicates that the client is providing message integrity in the MIC field (section 2.2.1.3) in the AUTHENTICATE_MESSAGE.<12>
// 0x00000004: indicates that the client is providing a target SPN generated from an untrusted source.<13>
MsvAvFlags
// A FILETIME structure ([MS-DTYP] section 2.3.1) in little-endian byte order that contains the server local time.<14>
MsvAvTimestamp
//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>
MsAvRestrictions
// The SPN of the target server. The name MUST be in Unicode and is not null-terminated.<16>
MsvAvTargetName
// 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).
// An all-zero value of the hash is used to indicate absence of channel bindings.<17>
MsvChannelBindings
)
// Helper struct that contains a list of AvPairs with helper methods for running through them
type AvPairs struct {
List []AvPair
}
func ReadAvPairs(data []byte) *AvPairs {
pairs := new(AvPairs)
// Get the number of AvPairs and allocate enough AvPair structures to hold them
offset := 0
for i := 0; len(data) > 0 && i < 11; i++ {
pair := ReadAvPair(data, offset)
offset = offset + 4 + int(pair.AvLen)
pairs.List = append(pairs.List, *pair)
if pair.AvId == MsvAvEOL {
break
}
}
return pairs
}
func (p *AvPairs) Bytes() (result []byte) {
totalLength := 0
for i := range p.List {
a := p.List[i]
totalLength = totalLength + int(a.AvLen) + 4
}
result = make([]byte, 0, totalLength)
for i := range p.List {
a := p.List[i]
result = append(result, a.Bytes()...)
}
return result
}
func (p *AvPairs) String() string {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("Av Pairs (Total %d pairs)\n", len(p.List)))
for i := range p.List {
buffer.WriteString(p.List[i].String())
buffer.WriteString("\n")
}
return buffer.String()
}
func (p *AvPairs) Find(avType AvPairType) (result *AvPair) {
for i := range p.List {
pair := p.List[i]
if avType == pair.AvId {
result = &pair
break
}
}
return
}
func (p *AvPairs) ByteValue(avType AvPairType) (result []byte) {
pair := p.Find(avType)
if pair != nil {
result = pair.Value
}
return
}
func (p *AvPairs) StringValue(avType AvPairType) (result string) {
pair := p.Find(avType)
if pair != nil {
result = pair.UnicodeStringValue()
}
return
}
// AvPair as described by MS-NLMP
type AvPair struct {
AvId AvPairType
AvLen uint16
Value []byte
}
func ReadAvPair(data []byte, offset int) *AvPair {
pair := new(AvPair)
pair.AvId = AvPairType(binary.LittleEndian.Uint16(data[offset : offset+2]))
pair.AvLen = binary.LittleEndian.Uint16(data[offset+2 : offset+4])
pair.Value = data[offset+4 : offset+4+int(pair.AvLen)]
return pair
}
func (a *AvPair) UnicodeStringValue() string {
return Utf16ToString(a.Value)
}
func (a *AvPair) Bytes() (result []byte) {
result = make([]byte, 4, a.AvLen+4)
result[0] = byte(a.AvId)
result[1] = byte(a.AvId >> 8)
result[2] = byte(a.AvLen)
result[3] = byte(a.AvLen >> 8)
result = append(result, a.Value...)
return
}
func (a *AvPair) String() string {
var outString string
switch a.AvId {
case MsvAvEOL:
outString = "MsvAvEOL"
case MsvAvNbComputerName, MsvAvNbDomainName, MsvAvDnsComputerName, MsvAvDnsDomainName, MsvAvDnsTreeName, MsvAvTargetName:
outString = fmt.Sprintf("%s: %s", reflect.TypeOf(a.AvId).Name(), a.UnicodeStringValue())
case MsvAvFlags, MsvAvTimestamp, MsAvRestrictions, MsvChannelBindings:
outString = fmt.Sprintf("%s: %s", reflect.TypeOf(a.AvId).Name(), hex.EncodeToString(a.Value))
default:
outString = fmt.Sprintf("unknown pair type: '%d'", a.AvId)
}
return outString
}

View File

@ -0,0 +1,138 @@
package messages
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
)
type Challenge struct {
// All bytes of the message
Bytes []byte
// sig - 8 bytes
Signature []byte
// message type - 4 bytes
MessageType uint32
// targetname - 12 bytes
TargetName *PayloadStruct
// negotiate flags - 4bytes
NegotiateFlags uint32
// server challenge - 8 bytes
ServerChallenge []byte
// MS-NLMP and Davenport disagree a little on the next few fields and how optional they are
// This is what Davenport has to say:
// As with the Type 1 message, there are a few versions of the Type 2 that have been observed:
//
// Version 1 -- The Context, Target Information, and OS Version structure are all omitted. The data block
// (containing only the contents of the Target Name security buffer) begins at offset 32. This form
// is seen in older Win9x-based systems, and is roughly documented in the Open Group's ActiveX reference
// documentation (Section 11.2.3).
//
// Version 2 -- The Context and Target Information fields are present, but the OS Version structure is not.
// The data block begins after the Target Information header, at offset 48. This form is seen in most out-of-box
// shipping versions of Windows.
//
// Version 3 -- The Context, Target Information, and OS Version structure are all present. The data block begins
// after the OS Version structure, at offset 56. Again, the buffers may be empty (yielding a zero-length data block).
// This form was introduced in a relatively recent Service Pack, and is seen on currently-patched versions of Windows 2000,
// Windows XP, and Windows 2003.
// reserved - 8 bytes (set to 0). This field is also known as 'context' in the davenport documentation
Reserved []byte
// targetinfo - 12 bytes
TargetInfoPayloadStruct *PayloadStruct
TargetInfo *AvPairs
// version - 8 bytes
Version *VersionStruct
// payload - variable
Payload []byte
PayloadOffset int
}
func ParseChallengeMessage(body []byte) (*Challenge, error) {
challenge := new(Challenge)
challenge.Signature = body[0:8]
if !bytes.Equal(challenge.Signature, []byte("NTLMSSP\x00")) {
return challenge, errors.New("Invalid NTLM message signature")
}
challenge.MessageType = binary.LittleEndian.Uint32(body[8:12])
if challenge.MessageType != 2 {
return challenge, errors.New("Invalid NTLM message type should be 0x00000002 for challenge message")
}
var err error
challenge.TargetName, err = ReadStringPayload(12, body)
if err != nil {
return challenge, err
}
challenge.NegotiateFlags = binary.LittleEndian.Uint32(body[20:24])
challenge.ServerChallenge = body[24:32]
challenge.Reserved = body[32:40]
challenge.TargetInfoPayloadStruct, err = ReadBytePayload(40, body)
if err != nil {
return challenge, err
}
challenge.TargetInfo = ReadAvPairs(challenge.TargetInfoPayloadStruct.Payload)
offset := 48
if NTLMSSP_NEGOTIATE_VERSION.IsSet(challenge.NegotiateFlags) {
challenge.Version, err = ReadVersionStruct(body[offset : offset+8])
if err != nil {
return challenge, err
}
offset = offset + 8
}
challenge.Payload = body[offset:]
challenge.PayloadOffset = offset
return challenge, nil
}
func (c *Challenge) getLowestPayloadOffset() int {
payloadStructs := [...]*PayloadStruct{c.TargetName, c.TargetInfoPayloadStruct}
// Find the lowest offset value
lowest := 9999
for i := range payloadStructs {
p := payloadStructs[i]
if p != nil && p.Offset > 0 && int(p.Offset) < lowest {
lowest = int(p.Offset)
}
}
return lowest
}
func (c *Challenge) String() string {
var buffer bytes.Buffer
buffer.WriteString("Challenge NTLM Message")
buffer.WriteString(fmt.Sprintf("\nPayload Offset: %d Lowest: %d Length: %d", c.PayloadOffset, c.getLowestPayloadOffset(), len(c.Payload)))
buffer.WriteString(fmt.Sprintf("\nTargetName: %s", c.TargetName.String()))
buffer.WriteString(fmt.Sprintf("\nServerChallenge: %s", hex.EncodeToString(c.ServerChallenge)))
if c.Version != nil {
buffer.WriteString(fmt.Sprintf("\nVersion: %s\n", c.Version.String()))
}
buffer.WriteString("\nTargetInfo")
buffer.WriteString(c.TargetInfo.String())
buffer.WriteString(fmt.Sprintf("\nFlags %d\n", c.NegotiateFlags))
buffer.WriteString(FlagsToString(c.NegotiateFlags))
return buffer.String()
}

View File

@ -0,0 +1,146 @@
package messages
import (
"bytes"
"encoding/hex"
"fmt"
)
// NTLMv1
// ******
type NtlmV1Response struct {
// 24 byte array
Response []byte
}
func (n *NtlmV1Response) String() string {
return fmt.Sprintf("NtlmV1Response: %s", hex.EncodeToString(n.Response))
}
func ReadNtlmV1Response(bytes []byte) *NtlmV1Response {
r := new(NtlmV1Response)
r.Response = bytes[0:24]
return r
}
// *** NTLMv2
// The NTLMv2_CLIENT_CHALLENGE structure defines the client challenge in the AUTHENTICATE_MESSAGE.
// This structure is used only when NTLM v2 authentication is configured.
type NtlmV2ClientChallenge struct {
// An 8-bit unsigned char that contains the current version of the challenge response type.
// This field MUST be 0x01.
RespType byte
// An 8-bit unsigned char that contains the maximum supported version of the challenge response type.
// This field MUST be 0x01.
HiRespType byte
// A 16-bit unsigned integer that SHOULD be 0x0000 and MUST be ignored on receipt.
Reserved1 uint16
// A 32-bit unsigned integer that SHOULD be 0x00000000 and MUST be ignored on receipt.
Reserved2 uint32
// A 64-bit unsigned integer that contains the current system time, represented as the number of 100 nanosecond
// ticks elapsed since midnight of January 1, 1601 (UTC).
TimeStamp []byte
// An 8-byte array of unsigned char that contains the client's ClientChallenge (section 3.1.5.1.2).
ChallengeFromClient []byte
// A 32-bit unsigned integer that SHOULD be 0x00000000 and MUST be ignored on receipt.
Reserved3 uint32
AvPairs *AvPairs
}
func (n *NtlmV2ClientChallenge) String() string {
var buffer bytes.Buffer
buffer.WriteString("NTLM v2 ClientChallenge\n")
buffer.WriteString(fmt.Sprintf("Timestamp: %s\n", hex.EncodeToString(n.TimeStamp)))
buffer.WriteString(fmt.Sprintf("ChallengeFromClient: %s\n", hex.EncodeToString(n.ChallengeFromClient)))
buffer.WriteString("AvPairs\n")
buffer.WriteString(n.AvPairs.String())
return buffer.String()
}
// The NTLMv2_RESPONSE structure defines the NTLMv2 authentication NtChallengeResponse in the AUTHENTICATE_MESSAGE.
// This response is used only when NTLMv2 authentication is configured.
type NtlmV2Response struct {
// A 16-byte array of unsigned char that contains the client's NT challenge- response as defined in section 3.3.2.
// Response corresponds to the NTProofStr variable from section 3.3.2.
Response []byte
// A variable-length byte array that contains the ClientChallenge as defined in section 3.3.2.
// ChallengeFromClient corresponds to the temp variable from section 3.3.2.
NtlmV2ClientChallenge *NtlmV2ClientChallenge
}
func (n *NtlmV2Response) String() string {
var buffer bytes.Buffer
buffer.WriteString("NTLM v2 Response\n")
buffer.WriteString(fmt.Sprintf("Response: %s\n", hex.EncodeToString(n.Response)))
buffer.WriteString(n.NtlmV2ClientChallenge.String())
return buffer.String()
}
func ReadNtlmV2Response(bytes []byte) *NtlmV2Response {
r := new(NtlmV2Response)
r.Response = bytes[0:16]
r.NtlmV2ClientChallenge = new(NtlmV2ClientChallenge)
c := r.NtlmV2ClientChallenge
c.RespType = bytes[16]
c.HiRespType = bytes[17]
// Ignoring - 2 bytes reserved
// c.Reserved1
// Ignoring - 4 bytes reserved
// c.Reserved2
c.TimeStamp = bytes[24:32]
c.ChallengeFromClient = bytes[32:40]
// Ignoring - 4 bytes reserved
// c.Reserved3
c.AvPairs = ReadAvPairs(bytes[44:])
return r
}
// LMv1
// ****
type LmV1Response struct {
// 24 bytes
Response []byte
}
func ReadLmV1Response(bytes []byte) *LmV1Response {
r := new(LmV1Response)
r.Response = bytes[0:24]
return r
}
func (l *LmV1Response) String() string {
return fmt.Sprintf("LmV1Response: %s", hex.EncodeToString(l.Response))
}
// *** LMv2
type LmV2Response struct {
// A 16-byte array of unsigned char that contains the client's LM challenge-response.
// This is the portion of the LmChallengeResponse field to which the HMAC_MD5 algorithm
/// has been applied, as defined in section 3.3.2. Specifically, Response corresponds
// to the result of applying the HMAC_MD5 algorithm, using the key ResponseKeyLM, to a
// message consisting of the concatenation of the ResponseKeyLM, ServerChallenge and ClientChallenge.
Response []byte
// An 8-byte array of unsigned char that contains the client's ClientChallenge, as defined in section 3.1.5.1.2.
ChallengeFromClient []byte
}
func ReadLmV2Response(bytes []byte) *LmV2Response {
r := new(LmV2Response)
r.Response = bytes[0:16]
r.ChallengeFromClient = bytes[16:24]
return r
}
func (l *LmV2Response) String() string {
var buffer bytes.Buffer
buffer.WriteString("LM v2 Response\n")
buffer.WriteString(fmt.Sprintf("Response: %s\n", hex.EncodeToString(l.Response)))
buffer.WriteString(fmt.Sprintf("ChallengeFromClient: %s\n", hex.EncodeToString(l.ChallengeFromClient)))
return buffer.String()
}

View File

@ -0,0 +1,49 @@
package messages
import (
"bytes"
"encoding/base64"
"encoding/hex"
"fmt"
"testing"
)
func TestDecodeChallenge(t *testing.T) {
challengeMessage := "TlRMTVNTUAACAAAAAAAAADgAAADzgpjiuaopAbx9ejQAAAAAAAAAAKIAogA4AAAABQLODgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA="
challengeData, err := base64.StdEncoding.DecodeString(challengeMessage)
if err != nil {
t.Error("Could not base64 decode message data")
}
challenge, err := ParseChallengeMessage(challengeData)
if err != nil || challenge == nil {
t.Error("Failed to parse challenge message " + err.Error())
}
if challenge.TargetName.Len != 0 || challenge.TargetName.MaxLen != 0 || challenge.TargetName.Offset != 56 {
values := fmt.Sprintf("TargetName Len:%v MaxLen:%v Offset:%v", challenge.TargetName.Len, challenge.TargetName.MaxLen, challenge.TargetName.Offset)
t.Error("Failed to parse Target Name in challenge: " + values)
}
if challenge.NegotiateFlags != uint32(3801645811) {
t.Errorf("Challenge negotiate flags not correct should be %v got %d", uint32(3801645811), challenge.NegotiateFlags)
}
serverChallenge, err := hex.DecodeString("B9AA2901BC7D7A34")
if !bytes.Equal(challenge.ServerChallenge, serverChallenge) {
hex := hex.EncodeToString(challenge.ServerChallenge)
t.Error("Server challenge is not correct '" + hex + "'")
}
if challenge.Version.ProductMajorVersion != 5 || challenge.Version.ProductMinorVersion != 2 || challenge.Version.ProductBuild != 3790 || challenge.Version.NTLMRevisionCurrent != 15 {
t.Error("Version information is not correct: '" + challenge.Version.String() + "'")
}
if len(challenge.Payload) != int(challenge.TargetInfoPayloadStruct.Len) {
t.Error("Payload length is not long enough")
}
challenge.String()
}

View File

@ -0,0 +1,37 @@
package messages
import (
"encoding/binary"
"unicode/utf16"
)
// Convert a UTF16 string to UTF8 string for Go usage
func Utf16ToString(bytes []byte) string {
var data []uint16
// 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
for offset := 0; offset < len(bytes); offset = offset + 2 {
i := binary.LittleEndian.Uint16(bytes[offset : offset+2])
data = append(data, i)
}
return string(utf16.Decode(data))
}
func StringToUtf16(value string) []byte {
result := make([]byte, len(value)*2)
stringBytes := []byte(value)
for i := 0; i < len(value); i++ {
result[i*2] = stringBytes[i]
}
return result
}
func Uint32ToBytes(v uint32) []byte {
bytes := make([]byte, 4)
bytes[0] = byte(v & 0xff)
bytes[1] = byte((v >> 8) & 0xff)
bytes[2] = byte((v >> 16) & 0xff)
bytes[3] = byte((v >> 24) & 0xff)
return bytes
}

View File

@ -0,0 +1,25 @@
package messages
type Negotiate struct {
// All bytes of the message
Bytes []byte
// sig - 8 bytes
Signature []byte
// message type - 4 bytes
MessageType uint32
// negotiate flags - 4bytes
NegotiateFlags uint32
// If the NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED flag is not set in NegotiateFlags,
// indicating that no DomainName is supplied in Payload - then this should have Len 0 / MaxLen 0
// this contains a domain name
DomainNameFields *PayloadStruct
// If the NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED flag is not set in NegotiateFlags,
// indicating that no WorkstationName is supplied in Payload - then this should have Len 0 / MaxLen 0
WorkstationFields *PayloadStruct
// version - 8 bytes
Version *VersionStruct
// payload - variable
Payload []byte
PayloadOffset int
}

View File

@ -0,0 +1,172 @@
package messages
// During NTLM authentication, each of the following flags is a possible value of the NegotiateFlags field of the NEGOTIATE_MESSAGE,
// CHALLENGE_MESSAGE, and AUTHENTICATE_MESSAGE, unless otherwise noted. These flags define client or server NTLM capabilities
// ssupported by the sender.
import (
"bytes"
"fmt"
"reflect"
)
type NegotiateFlag uint32
const (
// A (1 bit): If set, requests Unicode character set encoding. An alternate name for this field is NTLMSSP_NEGOTIATE_UNICODE.
NTLMSSP_NEGOTIATE_UNICODE NegotiateFlag = 1 << iota
// 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.
NTLM_NEGOTIATE_OEM
// The A and B bits are evaluated together as follows:
// A==1: The choice of character set encoding MUST be Unicode.
// A==0 and B==1: The choice of character set encoding MUST be OEM.
// A==0 and B==0: The protocol MUST return SEC_E_INVALID_TOKEN.
// 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.
NTLMSSP_REQUEST_TARGET
// r10 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R10
// D (1 bit): If set, requests session key negotiation for message signatures. If the client sends NTLMSSP_NEGOTIATE_SIGN to the server
// in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SIGN to the client in the CHALLENGE_MESSAGE. An alternate name
// for this field is NTLMSSP_NEGOTIATE_SIGN.
NTLMSSP_NEGOTIATE_SIGN
// E (1 bit): If set, requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL
// to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE.
// Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD always set NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128,
// if they are supported. An alternate name for this field is NTLMSSP_NEGOTIATE_SEAL.
NTLMSSP_NEGOTIATE_SEAL
// F (1 bit): If set, requests connectionless authentication. If NTLMSSP_NEGOTIATE_DATAGRAM is set, then NTLMSSP_NEGOTIATE_KEY_EXCH
// MUST always be set in the AUTHENTICATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. An alternate name for
// this field is NTLMSSP_NEGOTIATE_DATAGRAM.
NTLMSSP_NEGOTIATE_DATAGRAM
// G (1 bit): If set, requests LAN Manager (LM) session key computation. NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
// are mutually exclusive. If both NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are requested,
// NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client. NTLM v2 authentication session key generation
// MUST be supported by both the client and the DC in order to be used, and extended session security signing and sealing requires
// support from the client and the server to be used. An alternate name for this field is NTLMSSP_NEGOTIATE_LM_KEY.
NTLMSSP_NEGOTIATE_LM_KEY
// r9 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R9
// H (1 bit): If set, requests usage of the NTLM v1 session security protocol. NTLMSSP_NEGOTIATE_NTLM MUST be set in the
// NEGOTIATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. An alternate name for this field is NTLMSSP_NEGOTIATE_NTLM.
NTLMSSP_NEGOTIATE_NTLM
// r8 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R8
// J (1 bit): If set, the connection SHOULD be anonymous.<26> r8 (1 bit): This bit is unused and SHOULD be zero.<27>
NTLMSSP_ANONYMOUS
// 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.
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED
// L (1 bit): This flag indicates whether the Workstation field is present. If this flag is not set, the Workstation field
// MUST be ignored. If this flag is set, the length field of the Workstation field specifies whether the workstation name
// is nonempty or not.<24> An alternate name for this field is NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED.
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED
// r7 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R7
// M (1 bit): If set, requests the presence of a signature block on all messages. NTLMSSP_NEGOTIATE_ALWAYS_SIGN MUST be
// set in the NEGOTIATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. NTLMSSP_NEGOTIATE_ALWAYS_SIGN is
// overridden by NTLMSSP_NEGOTIATE_SIGN and NTLMSSP_NEGOTIATE_SEAL, if they are supported. An alternate name for this field
// is NTLMSSP_NEGOTIATE_ALWAYS_SIGN.
NTLMSSP_NEGOTIATE_ALWAYS_SIGN
// N (1 bit): If set, TargetName MUST be a domain name. The data corresponding to this flag is provided by the server in the
// TargetName field of the CHALLENGE_MESSAGE. If set, then NTLMSSP_TARGET_TYPE_SERVER MUST NOT be set. This flag MUST be ignored
// in the NEGOTIATE_MESSAGE and the AUTHENTICATE_MESSAGE. An alternate name for this field is NTLMSSP_TARGET_TYPE_DOMAIN.
NTLMSSP_TARGET_TYPE_DOMAIN
// O (1 bit): If set, TargetName MUST be a server name. The data corresponding to this flag is provided by the server in the
// TargetName field of the CHALLENGE_MESSAGE. If this bit is set, then NTLMSSP_TARGET_TYPE_DOMAIN MUST NOT be set. This flag MUST
// be ignored in the NEGOTIATE_MESSAGE and the AUTHENTICATE_MESSAGE. An alternate name for this field is NTLMSSP_TARGET_TYPE_SERVER.
NTLMSSP_TARGET_TYPE_SERVER
// r6 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R6
// P (1 bit): If set, requests usage of the NTLM v2 session security. NTLM v2 session security is a misnomer because it is not
// NTLM v2. It is NTLM v1 using the extended session security that is also in NTLM v2. NTLMSSP_NEGOTIATE_LM_KEY and
// NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are mutually exclusive. If both NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and
// NTLMSSP_NEGOTIATE_LM_KEY are requested, NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client.
// NTLM v2 authentication session key generation MUST be supported by both the client and the DC in order to be used, and extended
// session security signing and sealing requires support from the client and the server in order to be used.<23> An alternate name
// for this field is NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
// Q (1 bit): If set, requests an identify level token. An alternate name for this field is NTLMSSP_NEGOTIATE_IDENTIFY.
NTLMSSP_NEGOTIATE_IDENTIFY
// r5 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R5
// 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.
NTLMSSP_REQUEST_NON_NT_SESSION_KEY
// S (1 bit): If set, indicates that the TargetInfo fields in the CHALLENGE_MESSAGE (section 2.2.1.2) are populated. An alternate
// name for this field is NTLMSSP_NEGOTIATE_TARGET_INFO.
NTLMSSP_NEGOTIATE_TARGET_INFO
// r4 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R4
// T (1 bit): If set, requests the protocol version number. The data corresponding to this flag is provided in the Version field of the
// NEGOTIATE_MESSAGE, the CHALLENGE_MESSAGE, and the AUTHENTICATE_MESSAGE.<22> An alternate name for this field is NTLMSSP_NEGOTIATE_VERSION.
NTLMSSP_NEGOTIATE_VERSION
// r3 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R3
// r2 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R2
// r1 (1 bit): This bit is unused and MUST be zero.
NTLMSSP_R1
// U (1 bit): If set, requests 128-bit session key negotiation. An alternate name for this field is NTLMSSP_NEGOTIATE_128. If the client
// sends NTLMSSP_NEGOTIATE_128 to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_128 to the client in the
// CHALLENGE_MESSAGE only if the client sets NTLMSSP_NEGOTIATE_SEAL or NTLMSSP_NEGOTIATE_SIGN. Otherwise it is ignored. If both
// NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 are requested and supported by the client and server, NTLMSSP_NEGOTIATE_56 and
// NTLMSSP_NEGOTIATE_128 will both be returned to the client. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD set
// NTLMSSP_NEGOTIATE_128 if it is supported. An alternate name for this field is NTLMSSP_NEGOTIATE_128.<21>
NTLMSSP_NEGOTIATE_128
// V (1 bit): If set, requests an explicit key exchange. This capability SHOULD be used because it improves security for message integrity or
// 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.
NTLMSSP_NEGOTIATE_KEY_EXCH
// If set, requests 56-bit encryption. If the client sends NTLMSSP_NEGOTIATE_SEAL or NTLMSSP_NEGOTIATE_SIGN with NTLMSSP_NEGOTIATE_56 to the
// server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_56 to the client in the CHALLENGE_MESSAGE. Otherwise it is ignored.
// If both NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 are requested and supported by the client and server, NTLMSSP_NEGOTIATE_56 and
// NTLMSSP_NEGOTIATE_128 will both be returned to the client. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD set NTLMSSP_NEGOTIATE_56
// if it is supported. An alternate name for this field is NTLMSSP_NEGOTIATE_56.
NTLMSSP_NEGOTIATE_56
)
func (f NegotiateFlag) Set(flags uint32) uint32 {
return flags | uint32(f)
}
func (f NegotiateFlag) IsSet(flags uint32) bool {
return (flags & uint32(f)) != 0
}
func (f NegotiateFlag) Unset(flags uint32) uint32 {
return flags &^ uint32(f)
}
func (f NegotiateFlag) String() string {
return reflect.TypeOf(f).Name()
}
func FlagsToString(flags uint32) string {
allFlags := [...]NegotiateFlag{
NTLMSSP_NEGOTIATE_56,
NTLMSSP_NEGOTIATE_KEY_EXCH,
NTLMSSP_NEGOTIATE_128,
NTLMSSP_NEGOTIATE_VERSION,
NTLMSSP_NEGOTIATE_TARGET_INFO,
NTLMSSP_REQUEST_NON_NT_SESSION_KEY,
NTLMSSP_NEGOTIATE_IDENTIFY,
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY,
NTLMSSP_TARGET_TYPE_SERVER,
NTLMSSP_TARGET_TYPE_DOMAIN,
NTLMSSP_NEGOTIATE_ALWAYS_SIGN,
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED,
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED,
NTLMSSP_ANONYMOUS,
NTLMSSP_NEGOTIATE_NTLM,
NTLMSSP_NEGOTIATE_LM_KEY,
NTLMSSP_NEGOTIATE_DATAGRAM,
NTLMSSP_NEGOTIATE_SEAL,
NTLMSSP_NEGOTIATE_SIGN,
NTLMSSP_REQUEST_TARGET,
NTLM_NEGOTIATE_OEM,
NTLMSSP_NEGOTIATE_UNICODE}
var buffer bytes.Buffer
for i := range allFlags {
f := allFlags[i]
buffer.WriteString(fmt.Sprintf("%s: %v\n", f.String(), f.IsSet(flags)))
}
return buffer.String()
}

View File

@ -0,0 +1,29 @@
package messages
import (
"encoding/binary"
"encoding/hex"
"testing"
)
func TestFlags(t *testing.T) {
// Sample value from 4.2.2 NTLM v1 Authentication
bytes, _ := hex.DecodeString("338202e2")
flags := uint32(0)
flags = NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
flags = NTLMSSP_NEGOTIATE_56.Set(flags)
flags = NTLMSSP_NEGOTIATE_128.Set(flags)
flags = NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = NTLMSSP_TARGET_TYPE_SERVER.Set(flags)
flags = NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = NTLMSSP_NEGOTIATE_SEAL.Set(flags)
flags = NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = NTLM_NEGOTIATE_OEM.Set(flags)
flags = NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
if flags != binary.LittleEndian.Uint32(bytes) {
t.Error("NTLM Flags are not correct")
}
}

View File

@ -0,0 +1,92 @@
package messages
import (
"bytes"
"encoding/binary"
"encoding/hex"
)
const (
UnicodeStringPayload = iota
OemStringPayload
BytesPayload
)
type PayloadStruct struct {
Type int
Len uint16
MaxLen uint16
Offset uint32
Payload []byte
}
func (p *PayloadStruct) Bytes() []byte {
dest := make([]byte, 0, 8)
buffer := bytes.NewBuffer(dest)
binary.Write(buffer, binary.LittleEndian, p.Len)
binary.Write(buffer, binary.LittleEndian, p.MaxLen)
binary.Write(buffer, binary.LittleEndian, p.Offset)
return buffer.Bytes()
}
func (p *PayloadStruct) String() string {
var returnString string
switch p.Type {
case UnicodeStringPayload:
returnString = Utf16ToString(p.Payload)
case OemStringPayload:
returnString = string(p.Payload)
case BytesPayload:
returnString = hex.EncodeToString(p.Payload)
default:
returnString = "unknown type"
}
return returnString
}
func CreateBytePayload(bytes []byte) (*PayloadStruct, error) {
p := new(PayloadStruct)
p.Type = BytesPayload
p.Len = uint16(len(bytes))
p.MaxLen = uint16(len(bytes))
p.Payload = bytes // TODO: Copy these bytes instead of keeping a reference
return p, nil
}
func CreateStringPayload(value string) (*PayloadStruct, error) {
// Create UTF16 unicode bytes from string
bytes := StringToUtf16(value)
p := new(PayloadStruct)
p.Type = UnicodeStringPayload
p.Len = uint16(len(bytes))
p.MaxLen = uint16(len(bytes))
p.Payload = bytes // TODO: Copy these bytes instead of keeping a reference
return p, nil
}
func ReadStringPayload(startByte int, bytes []byte) (*PayloadStruct, error) {
return ReadPayloadStruct(startByte, bytes, UnicodeStringPayload)
}
func ReadBytePayload(startByte int, bytes []byte) (*PayloadStruct, error) {
return ReadPayloadStruct(startByte, bytes, BytesPayload)
}
func ReadPayloadStruct(startByte int, bytes []byte, PayloadType int) (*PayloadStruct, error) {
p := new(PayloadStruct)
p.Type = PayloadType
p.Len = binary.LittleEndian.Uint16(bytes[startByte : startByte+2])
p.MaxLen = binary.LittleEndian.Uint16(bytes[startByte+2 : startByte+4])
p.Offset = binary.LittleEndian.Uint32(bytes[startByte+4 : startByte+8])
if p.Len > 0 {
endOffset := p.Offset + uint32(p.Len)
p.Payload = bytes[p.Offset:endOffset]
}
return p, nil
}

View File

@ -0,0 +1,44 @@
package messages
import (
"bytes"
"encoding/binary"
"fmt"
)
type VersionStruct struct {
ProductMajorVersion uint8
ProductMinorVersion uint8
ProductBuild uint16
Reserved []byte
NTLMRevisionCurrent uint8
}
func ReadVersionStruct(structSource []byte) (*VersionStruct, error) {
versionStruct := new(VersionStruct)
versionStruct.ProductMajorVersion = uint8(structSource[0])
versionStruct.ProductMinorVersion = uint8(structSource[1])
versionStruct.ProductBuild = binary.LittleEndian.Uint16(structSource[2:4])
versionStruct.Reserved = structSource[4:7]
versionStruct.NTLMRevisionCurrent = uint8(structSource[7])
return versionStruct, nil
}
func (v *VersionStruct) String() string {
return fmt.Sprintf("%d.%d.%d Ntlm %d", v.ProductMajorVersion, v.ProductMinorVersion, v.ProductBuild, v.NTLMRevisionCurrent)
}
func (v *VersionStruct) Bytes() []byte {
dest := make([]byte, 0, 8)
buffer := bytes.NewBuffer(dest)
binary.Write(buffer, binary.LittleEndian, v.ProductMajorVersion)
binary.Write(buffer, binary.LittleEndian, v.ProductMinorVersion)
binary.Write(buffer, binary.LittleEndian, v.ProductBuild)
buffer.Write(make([]byte, 3))
binary.Write(buffer, binary.LittleEndian, uint8(0x0F))
return buffer.Bytes()
}

118
src/ntlm/ntlm.go Normal file
View File

@ -0,0 +1,118 @@
// Package NTLM implements the interfaces used for interacting with NTLMv1 and NTLMv2.
// To create NTLM v1 or v2 sessions you would use CreateClientSession and create ClientServerSession.
package ntlm
import (
rc4P "crypto/rc4"
"errors"
"ntlm/messages"
)
type Version int
const (
Version1 Version = 1
Version2 Version = 2
)
type Mode int
const (
ConnectionlessMode Mode = iota
ConnectionOrientedMode
)
// Creates an NTLM v1 or v2 client
// mode - This must be ConnectionlessMode or ConnectionOrientedMode depending on what type of NTLM is used
// version - This must be Version1 or Version2 depending on the version of NTLM used
func CreateClientSession(version Version, mode Mode) (n ClientSession, err error) {
switch version {
case Version1:
n = new(V1ClientSession)
case Version2:
n = new(V2ClientSession)
default:
return nil, errors.New("Unknown NTLM Version, must be 1 or 2")
}
return n, nil
}
type ClientSession interface {
SetUserInfo(username string, password string, domain string)
SetMode(mode Mode)
GenerateNegotiateMessage() (*messages.Negotiate, error)
ProcessChallengeMessage(*messages.Challenge) error
GenerateAuthenticateMessage() (*messages.Authenticate, error)
Seal(message []byte) ([]byte, error)
Sign(message []byte) ([]byte, error)
Mac(message []byte, sequenceNumber int) ([]byte, error)
}
// Creates an NTLM v1 or v2 server
// mode - This must be ConnectionlessMode or ConnectionOrientedMode depending on what type of NTLM is used
// version - This must be Version1 or Version2 depending on the version of NTLM used
func CreateServerSession(version Version, mode Mode) (n ServerSession, err error) {
switch version {
case Version1:
n = new(V1ServerSession)
case Version2:
n = new(V2ServerSession)
default:
return nil, errors.New("Unknown NTLM Version, must be 1 or 2")
}
n.SetMode(mode)
return n, nil
}
type ServerSession interface {
SetUserInfo(username string, password string, domain string)
SetMode(mode Mode)
ProcessNegotiateMessage(*messages.Negotiate) error
GenerateChallengeMessage() (*messages.Challenge, error)
ProcessAuthenticateMessage(*messages.Authenticate) error
Seal(message []byte) ([]byte, error)
Sign(message []byte) ([]byte, error)
Mac(message []byte, sequenceNumber int) ([]byte, error)
}
// This struct collects NTLM data structures and keys that are used across all types of NTLM requests
type SessionData struct {
mode Mode
user string
password string
userDomain string
negotiateFlags uint32
negotiateMessage *messages.Negotiate
challengeMessage *messages.Challenge
authenticateMessage *messages.Authenticate
serverChallenge []byte
clientChallenge []byte
ntChallengeResponse []byte
lmChallengeResponse []byte
responseKeyLM []byte
responseKeyNT []byte
exportedSessionKey []byte
encryptedRandomSessionKey []byte
keyExchangeKey []byte
sessionBaseKey []byte
mic []byte
clientSigningKey []byte
serverSigningKey []byte
clientSealingKey []byte
serverSealingKey []byte
clientHandle *rc4P.Cipher
serverHandle *rc4P.Cipher
}

321
src/ntlm/ntlmv1.go Normal file
View File

@ -0,0 +1,321 @@
// Receve an Authenticate message and authenticate the user
package ntlm
import (
"bytes"
"errors"
"ntlm/messages"
"strings"
)
/*******************************
Shared Session Data and Methods
*******************************/
type V1Session struct {
SessionData
}
func (n *V1Session) SetUserInfo(username string, password string, domain string) {
n.user = username
n.password = password
n.userDomain = domain
}
func (n *V1Session) SetMode(mode Mode) {
n.mode = mode
}
func (n *V1Session) fetchResponseKeys() (err error) {
n.responseKeyLM, err = lmowfv1(n.password)
if err != nil {
return err
}
n.responseKeyNT = ntowfv1(n.password)
return
}
func (n *V1Session) computeExpectedResponses() (err error) {
if messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(n.negotiateFlags) {
n.ntChallengeResponse, err = desL(n.responseKeyNT, md5(concat(n.serverChallenge, n.clientChallenge))[0:8])
if err != nil {
return err
}
n.lmChallengeResponse = concat(n.clientChallenge, make([]byte, 16))
} else {
n.ntChallengeResponse, err = desL(n.responseKeyNT, n.serverChallenge)
if err != nil {
return err
}
// NoLMResponseNTLMv1: A Boolean setting that controls using the NTLM response for the LM
// response to the server challenge when NTLMv1 authentication is used.<30>
// <30> Section 3.1.1.1: The default value of this state variable is TRUE. Windows NT Server 4.0 SP3
// does not support providing NTLM instead of LM responses.
noLmResponseNtlmV1 := false
if noLmResponseNtlmV1 {
n.lmChallengeResponse = n.ntChallengeResponse
} else {
n.lmChallengeResponse, err = desL(n.responseKeyLM, n.serverChallenge)
if err != nil {
return err
}
}
}
return nil
}
func (n *V1Session) computeSessionBaseKey() (err error) {
n.sessionBaseKey = md4(n.responseKeyNT)
return
}
func (n *V1Session) computeKeyExchangeKey() (err error) {
if messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(n.negotiateFlags) {
n.keyExchangeKey = hmacMd5(n.sessionBaseKey, concat(n.serverChallenge, n.lmChallengeResponse[0:8]))
} else {
n.keyExchangeKey, err = kxKey(n.negotiateFlags, n.sessionBaseKey, n.lmChallengeResponse, n.serverChallenge, n.responseKeyLM)
}
return
}
func (n *V1Session) calculateKeys() (err error) {
n.clientSigningKey = signKey(n.negotiateFlags, n.exportedSessionKey, "Client")
n.serverSigningKey = signKey(n.negotiateFlags, n.exportedSessionKey, "Server")
n.clientSealingKey = sealKey(n.negotiateFlags, n.exportedSessionKey, "Client")
n.serverSealingKey = sealKey(n.negotiateFlags, n.exportedSessionKey, "Server")
return
}
func (n *V1Session) Seal(message []byte) ([]byte, error) {
return nil, nil
}
func (n *V1Session) Sign(message []byte) ([]byte, error) {
return nil, nil
}
func (n *V1Session) Mac(message []byte, sequenceNumber int) ([]byte, error) {
sig := mac(n.negotiateFlags, n.serverHandle, n.serverSigningKey, uint32(sequenceNumber), message)
return sig.Bytes(), nil
}
/**************
Server Session
**************/
type V1ServerSession struct {
V1Session
}
func (n *V1ServerSession) ProcessNegotiateMessage(nm *messages.Negotiate) (err error) {
n.negotiateMessage = nm
return
}
func (n *V1ServerSession) GenerateChallengeMessage() (cm *messages.Challenge, err error) {
// TODO: Generate this challenge message
return
}
func (n *V1ServerSession) ProcessAuthenticateMessage(am *messages.Authenticate) (err error) {
n.authenticateMessage = am
n.negotiateFlags = am.NegotiateFlags
n.clientChallenge = am.ClientChallenge()
n.encryptedRandomSessionKey = am.EncryptedRandomSessionKey.Payload
err = n.fetchResponseKeys()
if err != nil {
return err
}
err = n.computeExpectedResponses()
if err != nil {
return err
}
err = n.computeSessionBaseKey()
if err != nil {
return err
}
err = n.computeKeyExchangeKey()
if err != nil {
return err
}
if !bytes.Equal(am.NtChallengeResponseFields.Payload, n.ntChallengeResponse) {
if !bytes.Equal(am.LmChallengeResponse.Payload, n.lmChallengeResponse) {
return errors.New("Could not authenticate")
}
}
n.mic = am.Mic
am.Mic = zeroBytes(16)
err = n.computeExportedSessionKey()
if err != nil {
return err
}
err = n.calculateKeys()
if err != nil {
return err
}
n.clientHandle, err = rc4Init(n.clientSealingKey)
if err != nil {
return err
}
n.serverHandle, err = rc4Init(n.serverSealingKey)
if err != nil {
return err
}
return nil
}
func (n *V1ServerSession) computeExportedSessionKey() (err error) {
if messages.NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.negotiateFlags) {
n.exportedSessionKey, err = rc4K(n.keyExchangeKey, n.encryptedRandomSessionKey)
if err != nil {
return err
}
// TODO: Calculate mic correctly. This calculation is not producing the right results now
// n.calculatedMic = HmacMd5(n.exportedSessionKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes))
} else {
n.exportedSessionKey = n.keyExchangeKey
// TODO: Calculate mic correctly. This calculation is not producing the right results now
// n.calculatedMic = HmacMd5(n.keyExchangeKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes))
}
return nil
}
/*************
Client Session
**************/
type V1ClientSession struct {
V1Session
}
func (n *V1ClientSession) GenerateNegotiateMessage() (nm *messages.Negotiate, err error) {
return nil, nil
}
func (n *V1ClientSession) ProcessChallengeMessage(cm *messages.Challenge) (err error) {
n.challengeMessage = cm
n.serverChallenge = cm.ServerChallenge
n.clientChallenge = randomBytes(8)
// Set up the default flags for processing the response. These are the flags that we will return
// in the authenticate message
flags := uint32(0)
flags = messages.NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
// NOTE: Unsetting this flag in order to get the server to generate the signatures we can recognize
// flags = messages.NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_DATAGRAM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = messages.NTLMSSP_REQUEST_TARGET.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
n.negotiateFlags = flags
err = n.fetchResponseKeys()
if err != nil {
return err
}
err = n.computeExpectedResponses()
if err != nil {
return err
}
err = n.computeSessionBaseKey()
if err != nil {
return err
}
err = n.computeKeyExchangeKey()
if err != nil {
return err
}
err = n.computeEncryptedSessionKey()
if err != nil {
return err
}
err = n.calculateKeys()
if err != nil {
return err
}
n.clientHandle, err = rc4Init(n.clientSealingKey)
if err != nil {
return err
}
n.serverHandle, err = rc4Init(n.serverSealingKey)
if err != nil {
return err
}
return nil
}
func (n *V1ClientSession) GenerateAuthenticateMessage() (am *messages.Authenticate, err error) {
am = new(messages.Authenticate)
am.Signature = []byte("NTLMSSP\x00")
am.MessageType = uint32(3)
am.LmChallengeResponse, _ = messages.CreateBytePayload(n.lmChallengeResponse)
am.NtChallengeResponseFields, _ = messages.CreateBytePayload(n.ntChallengeResponse)
am.DomainName, _ = messages.CreateStringPayload(n.userDomain)
am.UserName, _ = messages.CreateStringPayload(n.user)
am.Workstation, _ = messages.CreateStringPayload("SQUAREMILL")
am.EncryptedRandomSessionKey, _ = messages.CreateBytePayload(n.encryptedRandomSessionKey)
am.NegotiateFlags = n.negotiateFlags
am.Version = &messages.VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)}
return am, nil
}
func (n *V1ClientSession) computeEncryptedSessionKey() (err error) {
if messages.NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.negotiateFlags) {
n.exportedSessionKey = randomBytes(16)
n.encryptedRandomSessionKey, err = rc4K(n.keyExchangeKey, n.exportedSessionKey)
if err != nil {
return err
}
} else {
n.encryptedRandomSessionKey = n.keyExchangeKey
}
return nil
}
/********************************
NTLM V1 Password hash functions
*********************************/
func ntowfv1(passwd string) []byte {
return md4(utf16FromString(passwd))
}
// ConcatenationOf( DES( UpperCase( Passwd)[0..6],"KGS!@#$%"), DES( UpperCase( Passwd)[7..13],"KGS!@#$%"))
func lmowfv1(passwd string) ([]byte, error) {
asciiPassword := []byte(strings.ToUpper(passwd))
keyBytes := zeroPaddedBytes(asciiPassword, 0, 14)
first, err := des(keyBytes[0:7], []byte("KGS!@#$%"))
if err != nil {
return nil, err
}
second, err := des(keyBytes[7:14], []byte("KGS!@#$%"))
if err != nil {
return nil, err
}
return append(first, second...), nil
}

208
src/ntlm/ntlmv1_test.go Normal file
View File

@ -0,0 +1,208 @@
package ntlm
import (
"bytes"
"encoding/hex"
"ntlm/messages"
"testing"
)
func TestLMOWFv1(t *testing.T) {
// Sample from MS-NLMP
result, err := lmowfv1("Password")
expected, _ := hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d")
if err != nil || !bytes.Equal(result, expected) {
t.Errorf("LMNOWFv1 is not correct, got %s expected %s", hex.EncodeToString(result), "e52cac67419a9a224a3b108f3fa6cb6d")
}
}
func TestNTOWFv1(t *testing.T) {
// Sample from MS-NLMP
result := ntowfv1("Password")
expected, _ := hex.DecodeString("a4f49c406510bdcab6824ee7c30fd852")
if !bytes.Equal(result, expected) {
t.Error("NTOWFv1 is not correct")
}
}
func checkV1Value(t *testing.T, name string, value []byte, expected string, err error) {
if err != nil {
t.Errorf("NTLMv1 %s received error: %s", name, err)
} else {
expectedBytes, _ := hex.DecodeString(expected)
if !bytes.Equal(expectedBytes, value) {
t.Errorf("NTLMv1 %s is not correct got %s expected %s", name, hex.EncodeToString(value), expected)
}
}
}
func TestNtlmV1(t *testing.T) {
flags := uint32(0)
flags = messages.NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_56.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_128.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = messages.NTLMSSP_TARGET_TYPE_SERVER.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_SEAL.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = messages.NTLM_NEGOTIATE_OEM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
n := new(V1ClientSession)
n.SetUserInfo("User", "Password", "Domain")
n.negotiateFlags = flags
n.responseKeyNT, _ = hex.DecodeString("a4f49c406510bdcab6824ee7c30fd852")
n.responseKeyLM, _ = hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d")
n.clientChallenge, _ = hex.DecodeString("aaaaaaaaaaaaaaaa")
n.serverChallenge, _ = hex.DecodeString("0123456789abcdef")
var err error
// 4.2.2.1.3 Session Base Key and Key Exchange Key
err = n.computeSessionBaseKey()
checkV1Value(t, "sessionBaseKey", n.sessionBaseKey, "d87262b0cde4b1cb7499becccdf10784", err)
err = n.computeKeyExchangeKey()
checkV1Value(t, "keyExchangeKey", n.keyExchangeKey, "d87262b0cde4b1cb7499becccdf10784", err)
// 4.2.2.2.1 NTLMv1 Response
// NTChallengeResponse with With NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY not set
err = n.computeExpectedResponses()
checkV1Value(t, "NTChallengeResponse", n.ntChallengeResponse, "67c43011f30298a2ad35ece64f16331c44bdbed927841f94", err)
// 4.2.2.2.2 LMv1 Response
// The LmChallengeResponse is specified in section 3.3.1. With the NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag
// not set and with the NoLMResponseNTLMv1 flag not set
checkV1Value(t, "LMChallengeResponse", n.lmChallengeResponse, "98def7b87f88aa5dafe2df779688a172def11c7d5ccdef13", err)
// If the NTLMSSP_NEGOTIATE_LM_KEY flag is set then the KeyExchangeKey is:
n.negotiateFlags = messages.NTLMSSP_NEGOTIATE_LM_KEY.Set(n.negotiateFlags)
err = n.computeKeyExchangeKey()
checkV1Value(t, "keyExchangeKey with NTLMSSP_NEGOTIATE_LM_KEY", n.keyExchangeKey, "b09e379f7fbecb1eaf0afdcb0383c8a0", err)
n.negotiateFlags = messages.NTLMSSP_NEGOTIATE_LM_KEY.Unset(n.negotiateFlags)
// 4.2.2.2.3 Encrypted Session Key
//n.randomSessionKey, _ = hex.DecodeString("55555555555555555555555555555555")
// RC4 decryption of the EncryptedRandomSessionKey with the KeyExchange key
//err = n.computeKeyExchangeKey()
//n.encryptedRandomSessionKey, err = hex.DecodeString("518822b1b3f350c8958682ecbb3e3cb7")
//err = n.computeExportedSessionKey()
//checkV1Value(t, "ExportedSessionKey", n.exportedSessionKey, "55555555555555555555555555555555", err)
// NTLMSSP_REQUEST_NON_NT_SESSION_KEY is set:
n.negotiateFlags = messages.NTLMSSP_REQUEST_NON_NT_SESSION_KEY.Set(n.negotiateFlags)
err = n.computeKeyExchangeKey()
// n.encryptedRandomSessionKey, err = hex.DecodeString("7452ca55c225a1ca04b48fae32cf56fc")
// err = n.computeExportedSessionKey()
// checkV1Value(t, "ExportedSessionKey - NTLMSSP_REQUEST_NON_NT_SESSION_KEY", n.exportedSessionKey, "55555555555555555555555555555555", err)
n.negotiateFlags = messages.NTLMSSP_REQUEST_NON_NT_SESSION_KEY.Unset(n.negotiateFlags)
// NTLMSSP_NEGOTIATE_LM_KEY is set:
n.negotiateFlags = messages.NTLMSSP_NEGOTIATE_LM_KEY.Set(n.negotiateFlags)
err = n.computeKeyExchangeKey()
// n.encryptedRandomSessionKey, err = hex.DecodeString("4cd7bb57d697ef9b549f02b8f9b37864")
// err = n.computeExportedSessionKey()
// checkV1Value(t, "ExportedSessionKey - NTLMSSP_NEGOTIATE_LM_KEY", n.exportedSessionKey, "55555555555555555555555555555555", err)
n.negotiateFlags = messages.NTLMSSP_NEGOTIATE_LM_KEY.Unset(n.negotiateFlags)
// 4.2.2.3 Messages
challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033820a820123456789abcdef00000000000000000000000000000000060070170000000f530065007200760065007200")
challengeMessage, err := messages.ParseChallengeMessage(challengeMessageBytes)
if err == nil {
challengeMessage.String()
} else {
t.Errorf("Could not parse challenge message: %s", err)
}
client := new(V1ClientSession)
client.SetUserInfo("User", "Password", "Domain")
err = client.ProcessChallengeMessage(challengeMessage)
if err != nil {
t.Errorf("Could not process challenge message: %s", err)
}
server := new(V1ServerSession)
server.SetUserInfo("User", "Password", "Domain")
authenticateMessageBytes, err := hex.DecodeString("4e544c4d5353500003000000180018006c00000018001800840000000c000c00480000000800080054000000100010005c000000100010009c000000358280e20501280a0000000f44006f006d00610069006e00550073006500720043004f004d005000550054004500520098def7b87f88aa5dafe2df779688a172def11c7d5ccdef1367c43011f30298a2ad35ece64f16331c44bdbed927841f94518822b1b3f350c8958682ecbb3e3cb7")
authenticateMessage, err := messages.ParseAuthenticateMessage(authenticateMessageBytes, 1)
if err == nil {
authenticateMessage.String()
} else {
t.Errorf("Could not parse authenticate message: %s", err)
}
server = new(V1ServerSession)
server.SetUserInfo("User", "Password", "Domain")
server.serverChallenge = challengeMessage.ServerChallenge
err = server.ProcessAuthenticateMessage(authenticateMessage)
if err != nil {
t.Errorf("Could not process authenticate message: %s", err)
}
}
func TestNTLMv1WithClientChallenge(t *testing.T) {
flags := uint32(0)
flags = messages.NTLMSSP_NEGOTIATE_56.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags)
flags = messages.NTLMSSP_TARGET_TYPE_SERVER.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_SEAL.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = messages.NTLM_NEGOTIATE_OEM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
n := new(V1Session)
n.negotiateFlags = flags
n.responseKeyNT, _ = hex.DecodeString("a4f49c406510bdcab6824ee7c30fd852")
n.responseKeyLM, _ = hex.DecodeString("e52cac67419a9a224a3b108f3fa6cb6d")
n.clientChallenge, _ = hex.DecodeString("aaaaaaaaaaaaaaaa")
n.serverChallenge, _ = hex.DecodeString("0123456789abcdef")
var err error
// 4.2.2.1.3 Session Base Key and Key Exchange Key
err = n.computeExpectedResponses()
err = n.computeSessionBaseKey()
checkV1Value(t, "sessionBaseKey", n.sessionBaseKey, "d87262b0cde4b1cb7499becccdf10784", err)
checkV1Value(t, "LMv1Response", n.lmChallengeResponse, "aaaaaaaaaaaaaaaa00000000000000000000000000000000", err)
checkV1Value(t, "NTLMv1Response", n.ntChallengeResponse, "7537f803ae367128ca458204bde7caf81e97ed2683267232", err)
err = n.computeKeyExchangeKey()
checkV1Value(t, "keyExchangeKey", n.keyExchangeKey, "eb93429a8bd952f8b89c55b87f475edc", err)
challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033820a820123456789abcdef00000000000000000000000000000000060070170000000f530065007200760065007200")
challengeMessage, err := messages.ParseChallengeMessage(challengeMessageBytes)
if err == nil {
challengeMessage.String()
} else {
t.Errorf("Could not parse challenge message: %s", err)
}
client := new(V1ClientSession)
client.SetUserInfo("User", "Password", "Domain")
err = client.ProcessChallengeMessage(challengeMessage)
if err != nil {
t.Errorf("Could not process challenge message: %s", err)
}
server := new(V1ServerSession)
server.SetUserInfo("User", "Password", "Domain")
server.serverChallenge = challengeMessage.ServerChallenge
authenticateMessageBytes, _ := hex.DecodeString("4e544c4d5353500003000000180018006c00000018001800840000000c000c00480000000800080054000000100010005c000000000000009c000000358208820501280a0000000f44006f006d00610069006e00550073006500720043004f004d0050005500540045005200aaaaaaaaaaaaaaaa000000000000000000000000000000007537f803ae367128ca458204bde7caf81e97ed2683267232")
authenticateMessage, err := messages.ParseAuthenticateMessage(authenticateMessageBytes, 1)
if err == nil {
authenticateMessage.String()
} else {
t.Errorf("Could not parse authenticate message: %s", err)
}
err = server.ProcessAuthenticateMessage(authenticateMessage)
if err != nil {
t.Errorf("Could not process authenticate message: %s", err)
}
checkV1Value(t, "SealKey", server.clientSealingKey, "04dd7f014d8504d265a25cc86a3a7c06", nil)
checkV1Value(t, "SignKey", server.clientSigningKey, "60e799be5c72fc92922ae8ebe961fb8d", nil)
}

259
src/ntlm/ntlmv2.go Normal file
View File

@ -0,0 +1,259 @@
// Receve an Authenticate message and authenticate the user
package ntlm
import (
"bytes"
"errors"
"ntlm/messages"
"strings"
)
/*******************************
Shared Session Data and Methods
*******************************/
type V2Session struct {
SessionData
}
func (n *V2Session) SetUserInfo(username string, password string, domain string) {
n.user = username
n.password = password
n.userDomain = domain
}
func (n *V2Session) SetMode(mode Mode) {
n.mode = mode
}
func (n *V2Session) fetchResponseKeys() (err error) {
n.responseKeyLM = lmowfv2(n.user, n.password, n.userDomain)
n.responseKeyNT = ntowfv2(n.user, n.password, n.userDomain)
return
}
// Define ComputeResponse(NegFlg, ResponseKeyNT, ResponseKeyLM, CHALLENGE_MESSAGE.ServerChallenge, ClientChallenge, Time, ServerName)
// ServerNameBytes - The NtChallengeResponseFields.NTLMv2_RESPONSE.NTLMv2_CLIENT_CHALLENGE.AvPairs field structure of the AUTHENTICATE_MESSAGE payload.
func (n *V2Session) computeExpectedResponses(timestamp []byte, avPairBytes []byte) (err error) {
temp := concat([]byte{0x01}, []byte{0x01}, zeroBytes(6), timestamp, n.clientChallenge, zeroBytes(4), avPairBytes, zeroBytes(4))
ntProofStr := hmacMd5(n.responseKeyNT, concat(n.serverChallenge, temp))
n.ntChallengeResponse = concat(ntProofStr, temp)
n.lmChallengeResponse = concat(hmacMd5(n.responseKeyLM, concat(n.serverChallenge, n.clientChallenge)), n.clientChallenge)
n.sessionBaseKey = hmacMd5(n.responseKeyNT, ntProofStr)
return
}
func (n *V2Session) computeKeyExchangeKey() (err error) {
n.keyExchangeKey = n.sessionBaseKey
return
}
func (n *V2Session) calculateKeys() (err error) {
n.clientSigningKey = signKey(n.negotiateFlags, n.exportedSessionKey, "Client")
n.serverSigningKey = signKey(n.negotiateFlags, n.exportedSessionKey, "Server")
n.clientSealingKey = sealKey(n.negotiateFlags, n.exportedSessionKey, "Client")
n.serverSealingKey = sealKey(n.negotiateFlags, n.exportedSessionKey, "Server")
return
}
func (n *V2Session) Seal(message []byte) ([]byte, error) {
return nil, nil
}
func (n *V2Session) Sign(message []byte) ([]byte, error) {
return nil, nil
}
func (n *V2Session) Mac(message []byte,sequenceNumber int) ([]byte, error) {
return nil, nil
}
/**************
Server Session
**************/
type V2ServerSession struct {
V2Session
}
func (n *V2ServerSession) ProcessNegotiateMessage(nm *messages.Negotiate) (err error) {
n.negotiateMessage = nm
return
}
func (n *V2ServerSession) GenerateChallengeMessage() (cm *messages.Challenge, err error) {
// TODO: Generate this challenge message
return
}
func (n *V2ServerSession) ProcessAuthenticateMessage(am *messages.Authenticate) (err error) {
n.authenticateMessage = am
n.negotiateFlags = am.NegotiateFlags
n.clientChallenge = am.ClientChallenge()
n.encryptedRandomSessionKey = am.EncryptedRandomSessionKey.Payload
err = n.fetchResponseKeys()
if err != nil {
return err
}
timestamp := am.NtlmV2Response.NtlmV2ClientChallenge.TimeStamp
avPairsBytes := am.NtlmV2Response.NtlmV2ClientChallenge.AvPairs.Bytes()
err = n.computeExpectedResponses(timestamp, avPairsBytes)
if err != nil {
return err
}
err = n.computeKeyExchangeKey()
if err != nil {
return err
}
if !bytes.Equal(am.NtChallengeResponseFields.Payload, n.ntChallengeResponse) {
if !bytes.Equal(am.LmChallengeResponse.Payload, n.lmChallengeResponse) {
return errors.New("Could not authenticate")
}
}
n.mic = am.Mic
am.Mic = zeroBytes(16)
err = n.computeExportedSessionKey()
if err != nil {
return err
}
err = n.calculateKeys()
if err != nil {
return err
}
n.clientHandle, err = rc4Init(n.clientSealingKey)
if err != nil {
return err
}
n.serverHandle, err = rc4Init(n.serverSealingKey)
if err != nil {
return err
}
return nil
}
func (n *V2ServerSession) computeExportedSessionKey() (err error) {
if messages.NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.negotiateFlags) {
n.exportedSessionKey, err = rc4K(n.keyExchangeKey, n.encryptedRandomSessionKey)
if err != nil {
return err
}
// TODO: Calculate mic correctly. This calculation is not producing the right results now
// n.calculatedMic = HmacMd5(n.exportedSessionKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes))
} else {
n.exportedSessionKey = n.keyExchangeKey
// TODO: Calculate mic correctly. This calculation is not producing the right results now
// n.calculatedMic = HmacMd5(n.keyExchangeKey, concat(n.challengeMessage.Payload, n.authenticateMessage.Bytes))
}
return nil
}
/*************
Client Session
**************/
type V2ClientSession struct {
V2Session
}
func (n *V2ClientSession) GenerateNegotiateMessage() (nm *messages.Negotiate, err error) {
return nil, nil
}
func (n *V2ClientSession) ProcessChallengeMessage(cm *messages.Challenge) (err error) {
n.challengeMessage = cm
n.serverChallenge = cm.ServerChallenge
n.clientChallenge = randomBytes(8)
// Set up the default flags for processing the response. These are the flags that we will return
// in the authenticate message
flags := uint32(0)
flags = messages.NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_DATAGRAM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = messages.NTLMSSP_REQUEST_TARGET.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
n.negotiateFlags = flags
err = n.fetchResponseKeys()
if err != nil {
return err
}
// TODO: Create the AvPairs and timestamp
/*
//err = n.computeExpectedResponses()
//if err != nil { return err }
err = n.computeKeyExchangeKey()
if err != nil { return err }
err = n.computeEncryptedSessionKey()
if err != nil { return err }
err = n.calculateKeys()
if err != nil { return err }
n.clientHandle, err = rc4Init(n.clientSealingKey)
if err != nil { return err }
n.serverHandle, err = rc4Init(n.serverSealingKey)
if err != nil { return err }
*/
return nil
}
func (n *V2ClientSession) GenerateAuthenticateMessage() (am *messages.Authenticate, err error) {
am = new(messages.Authenticate)
am.Signature = []byte("NTLMSSP\x00")
am.MessageType = uint32(3)
am.LmChallengeResponse, _ = messages.CreateBytePayload(n.lmChallengeResponse)
am.NtChallengeResponseFields, _ = messages.CreateBytePayload(n.ntChallengeResponse)
am.DomainName, _ = messages.CreateStringPayload(n.userDomain)
am.UserName, _ = messages.CreateStringPayload(n.user)
am.Workstation, _ = messages.CreateStringPayload("SQUAREMILL")
am.EncryptedRandomSessionKey, _ = messages.CreateBytePayload(n.encryptedRandomSessionKey)
am.NegotiateFlags = n.negotiateFlags
am.Version = &messages.VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)}
return am, nil
}
func (n *V2ClientSession) computeEncryptedSessionKey() (err error) {
if messages.NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(n.negotiateFlags) {
n.exportedSessionKey = randomBytes(16)
n.encryptedRandomSessionKey, err = rc4K(n.keyExchangeKey, n.exportedSessionKey)
if err != nil {
return err
}
} else {
n.encryptedRandomSessionKey = n.keyExchangeKey
}
return nil
}
/********************************
NTLM V2 Password hash functions
*********************************/
// Define ntowfv2(Passwd, User, UserDom) as
func ntowfv2(user string, passwd string, userDom string) []byte {
concat := utf16FromString(strings.ToUpper(user) + userDom)
return hmacMd5(md4(utf16FromString(passwd)), concat)
}
// Define lmowfv2(Passwd, User, UserDom) as
func lmowfv2(user string, passwd string, userDom string) []byte {
return ntowfv2(user, passwd, userDom)
}

116
src/ntlm/ntlmv2_test.go Normal file
View File

@ -0,0 +1,116 @@
package ntlm
import (
"bytes"
"encoding/hex"
"ntlm/messages"
"strings"
"testing"
)
func checkV2Value(t *testing.T, name string, value []byte, expected string, err error) {
if err != nil {
t.Errorf("NTLMv2 %s received error: %s", name, err)
} else {
expectedBytes, _ := hex.DecodeString(expected)
if !bytes.Equal(expectedBytes, value) {
t.Errorf("NTLMv2 %s is not correct got %s expected %s", name, hex.EncodeToString(value), expected)
}
}
}
func TestNTOWFv2(t *testing.T) {
result := ntowfv2("User", "Password", "Domain")
// Sample value from 4.2.4.1.1 in MS-NLMP
expected, _ := hex.DecodeString("0c868a403bfd7a93a3001ef22ef02e3f")
if !bytes.Equal(result, expected) {
t.Errorf("NTOWFv2 is not correct got %s expected %s", hex.EncodeToString(result), "0c868a403bfd7a93a3001ef22ef02e3f")
}
}
func TestNTLMv2(t *testing.T) {
flags := uint32(0)
flags = messages.NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_56.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_128.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_VERSION.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags)
flags = messages.NTLMSSP_TARGET_TYPE_SERVER.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_NTLM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_SEAL.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_SIGN.Set(flags)
flags = messages.NTLM_NEGOTIATE_OEM.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_UNICODE.Set(flags)
// n := new(V2Session)
// n.SetUserInfo("User","Password","Domain")
// n.negotiateFlags = flags
// n.responseKeyNT, _ = hex.DecodeString("0c868a403bfd7a93a3001ef22ef02e3f")
// n.responseKeyLM = n.responseKeyNT
// n.clientChallenge, _ = hex.DecodeString("aaaaaaaaaaaaaaaa")
// n.serverChallenge, _ = hex.DecodeString("0123456789abcdef")
// Encrypted Random Session key
//c5 da d2 54 4f c9 79 90 94 ce 1c e9 0b c9 d0 3e
// Challenge message
client := new(V2ClientSession)
client.SetUserInfo("User", "Password", "Domain")
challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033828ae20123456789abcdef00000000000000002400240044000000060070170000000f53006500720076006500720002000c0044006f006d00610069006e0001000c0053006500720076006500720000000000")
challengeMessage, err := messages.ParseChallengeMessage(challengeMessageBytes)
if err == nil {
challengeMessage.String()
} else {
t.Errorf("Could not parse challenge message: %s", err)
}
err = client.ProcessChallengeMessage(challengeMessage)
if err != nil {
t.Errorf("Could not process challenge message: %s", err)
}
server := new(V2ServerSession)
server.SetUserInfo("User", "Password", "Domain")
server.serverChallenge = challengeMessage.ServerChallenge
// Authenticate message
r := strings.NewReplacer("\n", "", "\t", "", " ", "")
authenticateMessageBytes, _ := hex.DecodeString(r.Replace(`
4e544c4d535350000300000018001800
6c00000054005400840000000c000c00
48000000080008005400000010001000
5c00000010001000d8000000358288e2
0501280a0000000f44006f006d006100
69006e00550073006500720043004f00
4d005000550054004500520086c35097
ac9cec102554764a57cccc19aaaaaaaa
aaaaaaaa68cd0ab851e51c96aabc927b
ebef6a1c010100000000000000000000
00000000aaaaaaaaaaaaaaaa00000000
02000c0044006f006d00610069006e00
01000c00530065007200760065007200
0000000000000000c5dad2544fc97990
94ce1ce90bc9d03e`))
authenticateMessage, err := messages.ParseAuthenticateMessage(authenticateMessageBytes, 2)
if err == nil {
authenticateMessage.String()
} else {
t.Errorf("Could not parse authenticate message: %s", err)
}
err = server.ProcessAuthenticateMessage(authenticateMessage)
if err != nil {
t.Errorf("Could not process authenticate message: %s", err)
}
checkV2Value(t, "SessionBaseKey", server.sessionBaseKey, "8de40ccadbc14a82f15cb0ad0de95ca3", nil)
checkV2Value(t, "NTChallengeResponse", server.ntChallengeResponse[0:16], "68cd0ab851e51c96aabc927bebef6a1c", nil)
checkV2Value(t, "LMChallengeResponse", server.lmChallengeResponse, "86c35097ac9cec102554764a57cccc19aaaaaaaaaaaaaaaa", nil)
checkV2Value(t, "client seal key", server.clientSealingKey, "59f600973cc4960a25480a7c196e4c58", nil)
checkV2Value(t, "client seal key", server.clientSigningKey, "4788dc861b4782f35d43fd98fe1a2d39", nil)
}

114
src/ntlm/signature.go Normal file
View File

@ -0,0 +1,114 @@
package ntlm
import (
rc4P "crypto/rc4"
"encoding/binary"
"encoding/hex"
"fmt"
"ntlm/messages"
)
type NtlmsspMessageSignature struct {
// A 32-bit unsigned integer that contains the signature version. This field MUST be 0x00000001.
Version []byte
// A 4-byte array that contains the random pad for the message.
RandomPad []byte
// A 4-byte array that contains the checksum for the message.
CheckSum []byte
// A 32-bit unsigned integer that contains the NTLM sequence number for this application message.
SeqNum []byte
}
func (n *NtlmsspMessageSignature) String() string {
return fmt.Sprintf("NtlmsspMessageSignature: %s", hex.EncodeToString(n.Bytes()))
}
func (n *NtlmsspMessageSignature) Bytes() []byte {
return concat(n.Version, n.RandomPad, n.CheckSum, n.SeqNum)
}
// Define SEAL(Handle, SigningKey, SeqNum, Message) as
func seal(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, message []byte) (sealedMessage []byte, sig *NtlmsspMessageSignature) {
sealedMessage = rc4(handle, message)
sig = mac(negFlags, handle, signingKey, uint32(seqNum), message)
return
}
// Define SIGN(Handle, SigningKey, SeqNum, Message) as
func sign(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, message []byte) []byte {
return concat(message, mac(negFlags, handle, signingKey, uint32(seqNum), message).Bytes())
}
func mac(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, message []byte) (result *NtlmsspMessageSignature) {
if messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(negFlags) {
result = macWithExtendedSessionSecurity(negFlags, handle, signingKey, seqNum, message)
} else {
result = macWithoutExtendedSessionSecurity(handle, seqNum, message)
}
return result
}
// Define MAC(Handle, SigningKey, SeqNum, Message) as
// Set NTLMSSP_MESSAGE_SIGNATURE.Version to 0x00000001
// Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to CRC32(Message)
// Set NTLMSSP_MESSAGE_SIGNATURE.RandomPad RC4(Handle, RandomPad)
// Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to RC4(Handle, NTLMSSP_MESSAGE_SIGNATURE.Checksum)
// Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to RC4(Handle, 0x00000000)
// If (connection oriented)
// Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to NTLMSSP_MESSAGE_SIGNATURE.SeqNum XOR SeqNum
// Set SeqNum to SeqNum + 1
// Else
// Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to NTLMSSP_MESSAGE_SIGNATURE.SeqNum XOR (application supplied SeqNum)
// EndIf
// Set NTLMSSP_MESSAGE_SIGNATURE.RandomPad to 0
// End
func macWithoutExtendedSessionSecurity(handle *rc4P.Cipher, seqNum uint32, message []byte) *NtlmsspMessageSignature {
sig := new(NtlmsspMessageSignature)
seqNumBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(seqNumBytes, seqNum)
sig.Version = []byte{0x01, 0x00, 0x00, 0x00}
sig.CheckSum = make([]byte, 4)
binary.LittleEndian.PutUint32(sig.CheckSum, crc32(message))
sig.RandomPad = rc4(handle, zeroBytes(4))
sig.CheckSum = rc4(handle, sig.CheckSum)
sig.SeqNum = rc4(handle, zeroBytes(4))
for i := 0; i < 4; i++ {
sig.SeqNum[i] = sig.SeqNum[i] ^ seqNumBytes[i]
}
sig.RandomPad = zeroBytes(4)
return sig
}
// Define MAC(Handle, SigningKey, SeqNum, Message) as
// Set NTLMSSP_MESSAGE_SIGNATURE.Version to 0x00000001
// if Key Exchange Key Negotiated
// Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to RC4(Handle, HMAC_MD5(SigningKey, ConcatenationOf(SeqNum, Message))[0..7])
// else
// Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to HMAC_MD5(SigningKey, ConcatenationOf(SeqNum, Message))[0..7]
// end
// Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to SeqNum
// Set SeqNum to SeqNum + 1
// EndDefine
func macWithExtendedSessionSecurity(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, message []byte) *NtlmsspMessageSignature {
sig := new(NtlmsspMessageSignature)
sig.Version = []byte{0x01, 0x00, 0x00, 0x00}
seqNumBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(seqNumBytes, seqNum)
sig.CheckSum = hmacMd5(signingKey, concat(seqNumBytes, message))[0:8]
if messages.NTLMSSP_NEGOTIATE_KEY_EXCH.IsSet(negFlags) {
sig.CheckSum = rc4(handle, sig.CheckSum)
}
sig.SeqNum = seqNumBytes
return sig
}
func reinitSealingKey(key []byte, sequenceNumber int) (handle *rc4P.Cipher, err error) {
seqNumBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(seqNumBytes, uint32(sequenceNumber))
newKey := md5(concat(key, seqNumBytes))
handle, err = rc4Init(newKey)
return handle, err
}

View File

@ -0,0 +1,65 @@
package ntlm
import (
"bytes"
"encoding/hex"
"ntlm/messages"
"testing"
)
func checkSigValue(t *testing.T, name string, value []byte, expected string, err error) {
if err != nil {
t.Errorf("Signature %s received error: %s", name, err)
} else {
expectedBytes, _ := hex.DecodeString(expected)
if !bytes.Equal(expectedBytes, value) {
t.Errorf("Signature %s is not correct got %s expected %s", name, hex.EncodeToString(value), expected)
}
}
}
// 4.2.2.4 GSS_WrapEx Examples
func TestSealWithoutExtendedSessionSecurity(t *testing.T) {
key, _ := hex.DecodeString("55555555555555555555555555555555")
handle, _ := rc4Init(key)
plaintext, _ := hex.DecodeString("50006c00610069006e007400650078007400")
seqNum := uint32(0)
flags := uint32(0)
sealed, sig := seal(flags, handle, nil, seqNum, plaintext)
checkSigValue(t, "Sealed message", sealed, "56fe04d861f9319af0d7238a2e3b4d457fb8", nil)
checkSigValue(t, "Randompad", sig.RandomPad, "00000000", nil)
checkSigValue(t, "RC4 Checksum", sig.CheckSum, "09dcd1df", nil)
checkSigValue(t, "Xor Seq", sig.SeqNum, "2e459d36", nil)
}
func TestSealSignWithExtendedSessionSecurity(t *testing.T) {
sealKey, _ := hex.DecodeString("04dd7f014d8504d265a25cc86a3a7c06")
signKey, _ := hex.DecodeString("60e799be5c72fc92922ae8ebe961fb8d")
handle, _ := rc4Init(sealKey)
plaintext, _ := hex.DecodeString("50006c00610069006e007400650078007400")
seqNum := uint32(0)
flags := uint32(0)
flags = messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags)
sealed, sig := seal(flags, handle, signKey, seqNum, plaintext)
checkSigValue(t, "Sealed Data", sealed, "a02372f6530273f3aa1eb90190ce5200c99d", nil)
checkSigValue(t, "CheckSum", sig.CheckSum, "ff2aeb52f681793a", nil)
checkSigValue(t, "Signature", sig.Bytes(), "01000000ff2aeb52f681793a00000000", nil)
}
func TestSealSignWithExtendedSessionSecurityKeyEx(t *testing.T) {
sealKey, _ := hex.DecodeString("59f600973cc4960a25480a7c196e4c58")
signKey, _ := hex.DecodeString("4788dc861b4782f35d43fd98fe1a2d39")
handle, _ := rc4Init(sealKey)
plaintext, _ := hex.DecodeString("50006c00610069006e007400650078007400")
seqNum := uint32(0)
flags := uint32(0)
flags = messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags)
flags = messages.NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags)
sealed, sig := seal(flags, handle, signKey, seqNum, plaintext)
checkSigValue(t, "Sealed Data", sealed, "54e50165bf1936dc996020c1811b0f06fb5f", nil)
checkSigValue(t, "RC4 CheckSum", sig.CheckSum, "7fb38ec5c55d4976", nil)
checkSigValue(t, "Signature", sig.Bytes(), "010000007fb38ec5c55d497600000000", nil)
}