ntlm.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014 package require Tcl 8.2;
00015 package require SASL 1.0;
00016 package require des 1.0;
00017 package require md4;
00018
00019 namespace ::SASL {
00020 namespace NTLM {
00021 variable version 1.1.1
00022 variable rcsid {$Id: ntlm.tcl,v 1.8 2007/08/26 00:36:45 patthoyts Exp $}
00023 array NTLMFlags = {
00024 unicode 0x00000001
00025 oem 0x00000002
00026 req_target 0x00000004
00027 unknown 0x00000008
00028 sign 0x00000010
00029 seal 0x00000020
00030 datagram 0x00000040
00031 lmkey 0x00000080
00032 netware 0x00000100
00033 ntlm 0x00000200
00034 unknown 0x00000400
00035 unknown 0x00000800
00036 domain 0x00001000
00037 server 0x00002000
00038 share 0x00004000
00039 NTLM2 0x00008000
00040 targetinfo 0x00800000
00041 128bit 0x20000000
00042 keyexch 0x40000000
00043 56bit 0x80000000
00044 }
00045 }
00046 }
00047
00048
00049
00050 ret ::SASL::NTLM::NTLM (type context , type challenge , type args) {
00051 upvar #0 $context ctx
00052 incr ctx(step)
00053 switch -exact -- $ctx(step) {
00054
00055 1 {
00056 set ctx(realm) [eval [linsert $ctx(callback) end $context realm]]
00057 set ctx(hostname) [eval [linsert $ctx(callback) end $context hostname]]
00058 set ctx(response) [CreateGreeting $ctx(realm) $ctx(hostname)]
00059 set result 1
00060 }
00061
00062 2 {
00063 array set params [Decode $challenge]
00064 set user [eval [linsert $ctx(callback) end $context username]]
00065 set pass [eval [linsert $ctx(callback) end $context password]]
00066 if {[info exists params(domain)]} {
00067 set ctx(realm) $params(domain)
00068 }
00069 set ctx(response) [CreateResponse \
00070 $ctx(realm) $ctx(hostname) \
00071 $user $pass $params(nonce) $params(flags)]
00072 Decode $ctx(response)
00073 set result 0
00074 }
00075 default {
00076 return -code error "invalid state \"$ctx(step)"
00077 }
00078 }
00079 return $result
00080 }
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091 ret ::SASL::NTLM::CreateGreeting (type domainname , type hostname , optional flags ={)} {
00092 set domain [encoding convertto ascii $domainname]
00093 set host [encoding convertto ascii $hostname]
00094 set d_len [string length $domain]
00095 set h_len [string length $host]
00096 set d_off [expr {32 + $h_len}]
00097 if {[llength $flags] == 0} {
00098 flags = {unicode oem ntlm server domain req_target}
00099 }
00100 msg = [binary format a8iississi \
00101 "NTLMSSP\x00" 1 [Flags $flags] \
00102 $d_len $d_len $d_off \
00103 $h_len $h_len 32]
00104 append msg $host $domain
00105 return $msg
00106 }
00107
00108
00109
00110
00111
00112 ret ::SASL::NTLM::CreateChallenge (type domainname) {
00113 SASL::md5_init
00114 set target [encoding convertto ascii $domainname]
00115 set t_len [string length $target]
00116 set nonce [string range [binary format h* [SASL::CreateNonce]] 0 7]
00117 set pad [string repeat \0 8]
00118 set context [string repeat \0 8]
00119 set msg [binary format a8issii \
00120 "NTLMSSP\x00" 2 \
00121 $t_len $t_len 48 \
00122 [Flags {ntlm unicode}]]
00123 append msg $nonce $pad $context $pad $target
00124 return $msg
00125 }
00126
00127
00128
00129
00130 ret ::SASL::NTLM::CreateResponse (type domainname , type hostname , type username , type passwd , type nonce , type flags) {
00131 set lm_resp [LMhash $passwd $nonce]
00132 set nt_resp [NThash $passwd $nonce]
00133
00134 set domain [string toupper $domainname]
00135 set host [string toupper $hostname]
00136 set user $username
00137 set unicode [expr {$flags & 0x00000001}]
00138
00139 if {$unicode} {
00140 set domain [to_unicode_le $domain]
00141 set host [to_unicode_le $host]
00142 set user [to_unicode_le $user]
00143 }
00144
00145 set l_len [string length $lm_resp]; # LM response length
00146 set n_len [string length $nt_resp]; # NT response length
00147 set d_len [string length $domain]; # Domain name length
00148 set h_len [string length $host]; # Host name length
00149 set u_len [string length $user]; # User name length
00150 set s_len 0 ; # Session key length
00151
00152 # The offsets to strings appended to the structure
00153 set d_off [expr {0x40}]; # Fixed offset to Domain buffer
00154 set u_off [expr {$d_off + $d_len}]; # Offset to user buffer
00155 set h_off [expr {$u_off + $u_len}]; # Offset to host buffer
00156 set l_off [expr {$h_off + $h_len}]; # Offset to LM hash
00157 set n_off [expr {$l_off + $l_len}]; # Offset to NT hash
00158 set s_off [expr {$n_off + $n_len}]; # Offset to Session key
00159
00160 set msg [binary format a8is4s4s4s4s4s4i \
00161 "NTLMSSP\x00" 3 \
00162 [list $l_len $l_len $l_off 0] \
00163 [list $n_len $n_len $n_off 0] \
00164 [list $d_len $d_len $d_off 0] \
00165 [list $u_len $u_len $u_off 0] \
00166 [list $h_len $h_len $h_off 0] \
00167 [list $s_len $s_len $s_off 0] \
00168 $flags]
00169 append msg $domain $user $host $lm_resp $nt_resp
00170 return $msg
00171 }
00172
00173 ret ::SASL::NTLM::Debug (type msg) {
00174 array set d [Decode $msg]
00175 if {[info exists d(flags)]} {
00176 set d(flags) [list [format 0x%08x $d(flags)] [decodeflags $d(flags)]]
00177 }
00178 if {[info exists d(nonce)]} { set d(nonce) [base64::encode $d(nonce)] }
00179 if {[info exists d(lmhash)]} { set d(lmhash) [base64::encode $d(lmhash)] }
00180 if {[info exists d(nthash)]} { set d(nthash) [base64::encode $d(nthash)] }
00181 return [array get d]
00182 }
00183
00184 ret ::SASL::NTLM::Decode (type msg) {
00185 #puts [Debug $msg]
00186 binary scan $msg a7ci protocol zero type
00187
00188 switch -exact -- $type {
00189 1 {
00190 binary scan $msg @12ississi flags dlen dlen2 doff hlen hlen2 hoff
00191 binary scan $msg @${hoff}a${hlen} host
00192 binary scan $msg @${doff}a${dlen} domain
00193 return [list type $type flags [format 0x%08x $flags] \
00194 domain $domain host $host]
00195 }
00196 2 {
00197 binary scan $msg @12ssiia8a8 dlen dlen2 doff flags nonce pad
00198 set domain {}; binary scan $msg @${doff}a${dlen} domain
00199 set unicode [expr {$flags & 0x00000001}]
00200 if {$unicode} {
00201 set domain [from_unicode_le $domain]
00202 }
00203
00204 binary scan $nonce H* nonce_h
00205 binary scan $pad H* pad_h
00206 return [list type $type flags [format 0x%08x $flags] \
00207 domain $domain nonce $nonce]
00208 }
00209 3 {
00210 binary scan $msg @12ssissississississii \
00211 lmlen lmlen2 lmoff \
00212 ntlen ntlen2 ntoff \
00213 dlen dlen2 doff \
00214 ulen ulen2 uoff \
00215 hlen hlen2 hoff \
00216 slen slen2 soff \
00217 flags
00218 set domain {}; binary scan $msg @${doff}a${dlen} domain
00219 set user {}; binary scan $msg @${uoff}a${ulen} user
00220 set host {}; binary scan $msg @${hoff}a${hlen} host
00221 set unicode [expr {$flags & 0x00000001}]
00222 if {$unicode} {
00223 set domain [from_unicode_le $domain]
00224 set user [from_unicode_le $user]
00225 set host [from_unicode_le $host]
00226 }
00227 binary scan $msg @${ntoff}a${ntlen} ntdata
00228 binary scan $msg @${lmoff}a${lmlen} lmdata
00229 binary scan $ntdata H* ntdata_h
00230 binary scan $lmdata H* lmdata_h
00231 return [list type $type flags [format 0x%08x $flags]\
00232 domain $domain host $host user $user \
00233 lmhash $lmdata nthash $ntdata]
00234 }
00235 default {
00236 return -code error "invalid NTLM data: type not recognised"
00237 }
00238 }
00239 }
00240
00241 ret ::SASL::NTLM::decodeflags (type value) {
00242 variable NTLMFlags
00243 set result {}
00244 foreach {flag mask} [array get NTLMFlags] {
00245 if {$value & ($mask & 0xffffffff)} {
00246 lappend result $flag
00247 }
00248 }
00249 return $result
00250 }
00251
00252 ret ::SASL::NTLM::Flags (type flags) {
00253 variable NTLMFlags
00254 set result 0
00255 foreach flag $flags {
00256 if {![info exists NTLMFlags($flag)]} {
00257 return -code error "invalid ntlm flag \"$flag\""
00258 }
00259 set result [expr {$result | $NTLMFlags($flag)}]
00260 }
00261 return $result
00262 }
00263
00264
00265 ret ::SASL::NTLM::to_unicode_le (type str) {
00266 set result [encoding convertto unicode $str]
00267 if {[string equal $::tcl_platform(byteOrder) "bigEndian"]} {
00268 set r {} ; set n 0
00269 while {[binary scan $result @${n}cc a b] == 2} {
00270 append r [binary format cc $b $a]
00271 incr n 2
00272 }
00273 set result $r
00274 }
00275 return $result
00276 }
00277
00278
00279 ret ::SASL::NTLM::from_unicode_le (type str) {
00280 if {[string equal $::tcl_platform(byteOrder) "bigEndian"]} {
00281 set r {} ; set n 0
00282 while {[binary scan $str @${n}cc a b] == 2} {
00283 append r [binary format cc $b $a]
00284 incr n 2
00285 }
00286 set str $r
00287 }
00288 return [encoding convertfrom unicode $str]
00289 }
00290
00291 ret ::SASL::NTLM::LMhash (type password , type nonce) {
00292 set magic "\x4b\x47\x53\x21\x40\x23\x24\x25"
00293 set hash ""
00294 set password [string range [string toupper $password][string repeat \0 14] 0 13]
00295 foreach key [CreateDesKeys $password] {
00296 append hash [DES::des -dir encrypt -weak -mode ecb -key $key $magic]
00297 }
00298
00299 append hash [string repeat \0 5]
00300 set res ""
00301 foreach key [CreateDesKeys $hash] {
00302 append res [DES::des -dir encrypt -weak -mode ecb -key $key $nonce]
00303 }
00304
00305 return $res
00306 }
00307
00308 ret ::SASL::NTLM::NThash (type password , type nonce) {
00309 set pass [to_unicode_le $password]
00310 set hash [md4::md4 $pass]
00311 append hash [string repeat \x00 5]
00312
00313 set res ""
00314 foreach key [CreateDesKeys $hash] {
00315 append res [DES::des -dir encrypt -weak -mode ecb -key $key $nonce]
00316 }
00317
00318 return $res
00319 }
00320
00321
00322
00323
00324
00325 ret ::SASL::NTLM::CreateDesKeys (type key) {
00326 # pad to 7 byte boundary with nuls.
00327 set mod [expr {[string length $key] % 7}]
00328 if {$mod != 0} {
00329 append key [string repeat "\0" [expr {7 - $mod}]]
00330 }
00331 set len [string length $key]
00332 set r ""
00333 for {set n 0} {$n < $len} {incr n 7} {
00334 binary scan $key @${n}c7 bytes
00335 set b {}
00336 lappend b [expr { [lindex $bytes 0] & 0xFF}]
00337 lappend b [expr {(([lindex $bytes 0] & 0x01) << 7) | (([lindex $bytes 1] >> 1) & 0x7F)}]
00338 lappend b [expr {(([lindex $bytes 1] & 0x03) << 6) | (([lindex $bytes 2] >> 2) & 0x3F)}]
00339 lappend b [expr {(([lindex $bytes 2] & 0x07) << 5) | (([lindex $bytes 3] >> 3) & 0x1F)}]
00340 lappend b [expr {(([lindex $bytes 3] & 0x0F) << 4) | (([lindex $bytes 4] >> 4) & 0x0F)}]
00341 lappend b [expr {(([lindex $bytes 4] & 0x1F) << 3) | (([lindex $bytes 5] >> 5) & 0x07)}]
00342 lappend b [expr {(([lindex $bytes 5] & 0x3F) << 2) | (([lindex $bytes 6] >> 6) & 0x03)}]
00343 lappend b [expr {(([lindex $bytes 6] & 0x7F) << 1)}]
00344 lappend r [binary format c* $b]
00345 }
00346 return $r;
00347 }
00348
00349
00350 ret ::SASL::NTLM::CreateDesKeys2 (type key) {
00351 # pad to 7 byte boundary with nuls.
00352 append key [string repeat "\0" [expr {7 - ([string length $key] % 7)}]]
00353 binary scan $key B* bin
00354 set len [string length $bin]
00355 set r ""
00356 for {set n 0} {$n < $len} {incr n} {
00357 append r [string range $bin $n [incr n 6]] 0
00358 }
00359 # needs spliting into 8 byte keys.
00360 return [binary format B* $r]
00361 }
00362
00363
00364
00365
00366
00367 if {[llength [package provide SASL]] != 0} {
00368 ::SASL::register NTLM 50 ::SASL::NTLM::NTLM
00369 }
00370
00371 package provide SASL::NTLM $::SASL::NTLM::version
00372
00373
00374
00375
00376
00377
00378