package ntlm
import (
"crypto/hmac"
"crypto/md5"
"crypto/rc4"
"encoding/binary"
"hash"
"hash/crc32"
"golang.org/x/crypto/md4"
)
var zero [16 ]byte
var version = []byte {
0 : WINDOWS_MAJOR_VERSION_10 ,
1 : WINDOWS_MINOR_VERSION_0 ,
7 : NTLMSSP_REVISION_W2K3 ,
}
const defaultFlags = NTLMSSP_NEGOTIATE_56 | NTLMSSP_NEGOTIATE_KEY_EXCH | NTLMSSP_NEGOTIATE_128 | NTLMSSP_NEGOTIATE_TARGET_INFO | NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY | NTLMSSP_NEGOTIATE_ALWAYS_SIGN | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_VERSION
var le = binary .LittleEndian
const (
NtLmNegotiate = 0x00000001
NtLmChallenge = 0x00000002
NtLmAuthenticate = 0x00000003
)
const (
NTLMSSP_NEGOTIATE_UNICODE = 1 << iota
NTLM_NEGOTIATE_OEM
NTLMSSP_REQUEST_TARGET
_
NTLMSSP_NEGOTIATE_SIGN
NTLMSSP_NEGOTIATE_SEAL
NTLMSSP_NEGOTIATE_DATAGRAM
NTLMSSP_NEGOTIATE_LM_KEY
_
NTLMSSP_NEGOTIATE_NTLM
_
NTLMSSP_ANONYMOUS
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED
_
NTLMSSP_NEGOTIATE_ALWAYS_SIGN
NTLMSSP_TARGET_TYPE_DOMAIN
NTLMSSP_TARGET_TYPE_SERVER
_
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
NTLMSSP_NEGOTIATE_IDENTIFY
_
NTLMSSP_REQUEST_NON_NT_SESSION_KEY
NTLMSSP_NEGOTIATE_TARGET_INFO
_
NTLMSSP_NEGOTIATE_VERSION
_
_
_
NTLMSSP_NEGOTIATE_128
NTLMSSP_NEGOTIATE_KEY_EXCH
NTLMSSP_NEGOTIATE_56
)
const (
MsvAvEOL = iota
MsvAvNbComputerName
MsvAvNbDomainName
MsvAvDnsComputerName
MsvAvDnsDomainName
MsvAvDnsTreeName
MsvAvFlags
MsvAvTimestamp
MsvAvSingleHost
MsvAvTargetName
MsvAvChannelBindings
)
type addr struct {
typ uint32
val []byte
}
type channelBindings struct {
InitiatorAddress addr
AcceptorAddress addr
AppData []byte
}
var signature = []byte ("NTLMSSP\x00" )
const (
WINDOWS_MAJOR_VERSION_5 = 0x05
WINDOWS_MAJOR_VERSION_6 = 0x06
WINDOWS_MAJOR_VERSION_10 = 0x0a
)
const (
WINDOWS_MINOR_VERSION_0 = 0x00
WINDOWS_MINOR_VERSION_1 = 0x01
WINDOWS_MINOR_VERSION_2 = 0x02
WINDOWS_MINOR_VERSION_3 = 0x03
)
const (
NTLMSSP_REVISION_W2K3 = 0x0f
)
func ntowfv2(USER , password , domain []byte ) []byte {
h := md4 .New ()
h .Write (password )
hash := h .Sum (nil )
return ntowfv2Hash (USER , hash , domain )
}
func ntowfv2Hash(USER , hash , domain []byte ) []byte {
hm := hmac .New (md5 .New , hash )
hm .Write (USER )
hm .Write (domain )
return hm .Sum (nil )
}
func encodeNtlmv2Response(dst []byte , h hash .Hash , serverChallenge , clientChallenge , timeStamp []byte , targetInfo encoder ) {
ntlmv2ClientChallenge := dst [16 :]
ntlmv2ClientChallenge [0 ] = 1
ntlmv2ClientChallenge [1 ] = 1
copy (ntlmv2ClientChallenge [8 :16 ], timeStamp )
copy (ntlmv2ClientChallenge [16 :24 ], clientChallenge )
targetInfo .encode (ntlmv2ClientChallenge [28 :])
h .Write (serverChallenge )
h .Write (ntlmv2ClientChallenge )
h .Sum (dst [:0 ])
}
type encoder interface {
size() int
encode(bs []byte )
}
type bytesEncoder []byte
func (b bytesEncoder ) size () int {
return len (b )
}
func (b bytesEncoder ) encode (bs []byte ) {
copy (bs , b )
}
type targetInfoEncoder struct {
Info []byte
SPN []byte
InfoMap map [uint16 ][]byte
}
func newTargetInfoEncoder(info , spn []byte ) *targetInfoEncoder {
infoMap , ok := parseAvPairs (info )
if !ok {
return nil
}
return &targetInfoEncoder {
Info : info ,
SPN : spn ,
InfoMap : infoMap ,
}
}
func (i *targetInfoEncoder ) size () int {
size := len (i .Info )
if _ , ok := i .InfoMap [MsvAvFlags ]; !ok {
size += 8
}
size += 20
if len (i .SPN ) != 0 {
size += 4 + len (i .SPN )
}
return size
}
func (i *targetInfoEncoder ) encode (dst []byte ) {
var off int
if flags , ok := i .InfoMap [MsvAvFlags ]; ok {
le .PutUint32 (flags , le .Uint32 (flags )|0x02 )
off = copy (dst , i .Info [:len (i .Info )-4 ])
} else {
off = copy (dst , i .Info [:len (i .Info )-4 ])
le .PutUint16 (dst [off :off +2 ], MsvAvFlags )
le .PutUint16 (dst [off +2 :off +4 ], 4 )
le .PutUint32 (dst [off +4 :off +8 ], 0x02 )
off += 8
}
le .PutUint16 (dst [off :off +2 ], MsvAvChannelBindings )
le .PutUint16 (dst [off +2 :off +4 ], 16 )
off += 20
if len (i .SPN ) != 0 {
le .PutUint16 (dst [off :off +2 ], MsvAvTargetName )
le .PutUint16 (dst [off +2 :off +4 ], uint16 (len (i .SPN )))
copy (dst [off +4 :], i .SPN )
off += 4 + len (i .SPN )
}
le .PutUint16 (dst [off :off +2 ], MsvAvEOL )
le .PutUint16 (dst [off +2 :off +4 ], 0 )
off += 4
}
func mac(dst []byte , negotiateFlags uint32 , handle *rc4 .Cipher , signingKey []byte , seqNum uint32 , msg []byte ) ([]byte , uint32 ) {
ret , tag := sliceForAppend (dst , 16 )
if negotiateFlags &NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY == 0 {
le .PutUint32 (tag [:4 ], 0x00000001 )
le .PutUint32 (tag [8 :12 ], crc32 .ChecksumIEEE (msg ))
handle .XORKeyStream (tag [4 :8 ], tag [4 :8 ])
handle .XORKeyStream (tag [8 :12 ], tag [8 :12 ])
handle .XORKeyStream (tag [12 :16 ], tag [12 :16 ])
tag [12 ] ^= byte (seqNum )
tag [13 ] ^= byte (seqNum >> 8 )
tag [14 ] ^= byte (seqNum >> 16 )
tag [15 ] ^= byte (seqNum >> 24 )
if negotiateFlags &NTLMSSP_NEGOTIATE_DATAGRAM == 0 {
seqNum ++
}
tag [4 ] = 0
tag [5 ] = 0
tag [6 ] = 0
tag [7 ] = 0
} else {
le .PutUint32 (tag [:4 ], 0x00000001 )
le .PutUint32 (tag [12 :16 ], seqNum )
h := hmac .New (md5 .New , signingKey )
h .Write (tag [12 :16 ])
h .Write (msg )
copy (tag [4 :12 ], h .Sum (nil ))
if negotiateFlags &NTLMSSP_NEGOTIATE_KEY_EXCH != 0 {
handle .XORKeyStream (tag [4 :12 ], tag [4 :12 ])
}
seqNum ++
}
return ret , seqNum
}
func signKey(negotiateFlags uint32 , randomSessionKey []byte , fromClient bool ) []byte {
if negotiateFlags &NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY != 0 {
h := md5 .New ()
h .Write (randomSessionKey )
if fromClient {
h .Write ([]byte ("session key to client-to-server signing key magic constant\x00" ))
} else {
h .Write ([]byte ("session key to server-to-client signing key magic constant\x00" ))
}
return h .Sum (nil )
}
return nil
}
func sealKey(negotiateFlags uint32 , randomSessionKey []byte , fromClient bool ) []byte {
if negotiateFlags &NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY != 0 {
h := md5 .New ()
switch {
case negotiateFlags &NTLMSSP_NEGOTIATE_128 != 0 :
h .Write (randomSessionKey )
case negotiateFlags &NTLMSSP_NEGOTIATE_56 != 0 :
h .Write (randomSessionKey [:7 ])
default :
h .Write (randomSessionKey [:5 ])
}
if fromClient {
h .Write ([]byte ("session key to client-to-server sealing key magic constant\x00" ))
} else {
h .Write ([]byte ("session key to server-to-client sealing key magic constant\x00" ))
}
return h .Sum (nil )
}
if negotiateFlags &NTLMSSP_NEGOTIATE_LM_KEY != 0 {
sealingKey := make ([]byte , 8 )
if negotiateFlags &NTLMSSP_NEGOTIATE_56 != 0 {
copy (sealingKey , randomSessionKey [:7 ])
sealingKey [7 ] = 0xa0
} else {
copy (sealingKey , randomSessionKey [:5 ])
sealingKey [5 ] = 0xe5
sealingKey [6 ] = 0x38
sealingKey [7 ] = 0xb0
}
return sealingKey
}
return randomSessionKey
}
func parseAvPairs(bs []byte ) (pairs map [uint16 ][]byte , ok bool ) {
if len (bs ) < 4 {
return nil , false
}
for _ , c := range bs [len (bs )-4 :] {
if c != 0x00 {
return nil , false
}
}
pairs = make (map [uint16 ][]byte )
for len (bs ) > 0 {
if len (bs ) < 4 {
return nil , false
}
id := le .Uint16 (bs [:2 ])
n := int (le .Uint16 (bs [2 :4 ]))
if len (bs ) < 4 +n {
return nil , false
}
pairs [id ] = bs [4 : 4 +n ]
bs = bs [4 +n :]
}
return pairs , true
}
func sliceForAppend(in []byte , n int ) (head , tail []byte ) {
if total := len (in ) + n ; cap (in ) >= total {
head = in [:total ]
} else {
head = make ([]byte , total )
copy (head , in )
}
tail = head [len (in ):]
return
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .