ntlm.tcl

Go to the documentation of this file.
00001 /*  ntlm.tcl - Copyright (C) 2005 Pat Thoyts <patthoyts@users.sourceforge.net>*/
00002 /* */
00003 /*  This is an implementation of Microsoft's NTLM authentication mechanism.*/
00004 /* */
00005 /*  References:*/
00006 /*     http://www.innovation.ch/java/ntlm.html*/
00007 /*     http://davenport.sourceforge.net/ntlm.html*/
00008 /* */
00009 /*  -------------------------------------------------------------------------*/
00010 /*  See the file "license.terms" for information on usage and redistribution*/
00011 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00012 /*  -------------------------------------------------------------------------*/
00013 
00014 package require Tcl 8.2;                /*  tcl minimum version*/
00015 package require SASL 1.0;               /*  tcllib 1.7*/
00016 package require des 1.0;                /*  tcllib 1.8*/
00017 package require md4;                    /*  tcllib 1.4*/
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 /*  NTLM client implementation*/
00084 /*  -------------------------------------------------------------------------*/
00085 
00086 /*  The NMLM greeting. This is sent by the client to the server to initiate*/
00087 /*  the challenge response handshake.*/
00088 /*  This message contains the hostname (not domain qualified) and the */
00089 /*  NT domain name for authentication.*/
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 /*  Create a NTLM server challenge. This is sent by a server in response to*/
00109 /*  a client type 1 message. The content of the type 2 message is variable*/
00110 /*  and depends upon the flags set by the client and server choices.*/
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 /*  Compose the final client response. This contains the encoded username*/
00128 /*  and password, along with the server nonce value.*/
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 /*  Convert a string to unicode in little endian byte order.*/
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 /*  Convert a little-endian unicode string to utf-8.*/
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 /*  Convert a password into a 56 bit DES key according to the NTLM specs.*/
00322 /*  We do NOT fix the parity of each byte. If we did, then bit 0 of each*/
00323 /*  byte should be adjusted to give the byte odd parity.*/
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 /*  This is slower than the above in Tcl 8.4.9*/
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 /*  Register this SASL mechanism with the Tcllib SASL package.*/
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 /*  Local variables:*/
00376 /*  indent-tabs-mode: nil*/
00377 /*  End:*/
00378 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1