rubyntlm 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/.travis.yml +1 -7
- data/Gemfile +1 -1
- data/Gemfile.lock +12 -2
- data/README.md +1 -1
- data/Rakefile +17 -6
- data/lib/net/ntlm.rb +128 -64
- data/rubyntlm.gemspec +3 -1
- data/spec/unit/ntlm_spec.rb +175 -0
- metadata +14 -16
- data/test/function_test.rb +0 -111
data/.gitignore
ADDED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rubyntlm (0.
|
4
|
+
rubyntlm (0.3.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
diff-lcs (1.2.1)
|
9
10
|
rake (10.0.3)
|
11
|
+
rspec (2.13.0)
|
12
|
+
rspec-core (~> 2.13.0)
|
13
|
+
rspec-expectations (~> 2.13.0)
|
14
|
+
rspec-mocks (~> 2.13.0)
|
15
|
+
rspec-core (2.13.1)
|
16
|
+
rspec-expectations (2.13.0)
|
17
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
18
|
+
rspec-mocks (2.13.0)
|
10
19
|
|
11
20
|
PLATFORMS
|
21
|
+
java
|
12
22
|
ruby
|
13
23
|
|
14
24
|
DEPENDENCIES
|
15
|
-
bundler (~> 1.3)
|
16
25
|
rake
|
26
|
+
rspec
|
17
27
|
rubyntlm!
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Ruby/NTLM -- NTLM Authentication Library for Ruby
|
2
2
|
|
3
|
-
[](https://travis-ci.org/WinRb/rubyntlm)
|
4
4
|
|
5
5
|
Ruby/NTLM provides message creator and parser for the NTLM authentication.
|
6
6
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,21 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require 'rake/testtask'
|
3
2
|
|
4
|
-
task :default => [:
|
3
|
+
task :default => [:spec]
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
|
8
|
+
desc 'Default: run specs.'
|
9
|
+
task :default => :spec
|
10
|
+
|
11
|
+
desc "Run specs unit tests"
|
12
|
+
RSpec::Core::RakeTask.new do |t|
|
13
|
+
t.pattern = "./spec/unit/*_spec.rb"
|
10
14
|
end
|
15
|
+
|
16
|
+
desc "Generate code coverage"
|
17
|
+
RSpec::Core::RakeTask.new(:coverage) do |t|
|
18
|
+
t.pattern = "./spec/unit/*_spec.rb" # don't need this, it's default.
|
19
|
+
t.rcov = true
|
20
|
+
t.rcov_opts = ['--exclude', 'spec']
|
21
|
+
end
|
data/lib/net/ntlm.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
#
|
2
3
|
# = net/ntlm.rb
|
3
4
|
#
|
@@ -45,15 +46,14 @@
|
|
45
46
|
require 'base64'
|
46
47
|
require 'openssl'
|
47
48
|
require 'openssl/digest'
|
48
|
-
require 'kconv'
|
49
49
|
require 'socket'
|
50
50
|
|
51
|
-
module Net
|
52
|
-
module NTLM
|
53
|
-
|
54
|
-
module VERSION
|
51
|
+
module Net
|
52
|
+
module NTLM
|
53
|
+
# @private
|
54
|
+
module VERSION
|
55
55
|
MAJOR = 0
|
56
|
-
MINOR =
|
56
|
+
MINOR = 3
|
57
57
|
TINY = 0
|
58
58
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
59
59
|
end
|
@@ -68,14 +68,14 @@ module Net #:nodoc:
|
|
68
68
|
:UNICODE => 0x00000001,
|
69
69
|
:OEM => 0x00000002,
|
70
70
|
:REQUEST_TARGET => 0x00000004,
|
71
|
-
|
71
|
+
:MBZ9 => 0x00000008,
|
72
72
|
:SIGN => 0x00000010,
|
73
73
|
:SEAL => 0x00000020,
|
74
|
-
|
74
|
+
:NEG_DATAGRAM => 0x00000040,
|
75
75
|
:NETWARE => 0x00000100,
|
76
76
|
:NTLM => 0x00000200,
|
77
|
-
|
78
|
-
|
77
|
+
:NEG_NT_ONLY => 0x00000400,
|
78
|
+
:MBZ7 => 0x00000800,
|
79
79
|
:DOMAIN_SUPPLIED => 0x00001000,
|
80
80
|
:WORKSTATION_SUPPLIED => 0x00002000,
|
81
81
|
:LOCAL_CALL => 0x00004000,
|
@@ -85,7 +85,7 @@ module Net #:nodoc:
|
|
85
85
|
:NTLM2_KEY => 0x00080000,
|
86
86
|
:KEY128 => 0x20000000,
|
87
87
|
:KEY56 => 0x80000000
|
88
|
-
}
|
88
|
+
}.freeze
|
89
89
|
|
90
90
|
FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
|
91
91
|
|
@@ -95,24 +95,36 @@ module Net #:nodoc:
|
|
95
95
|
:TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
|
96
96
|
}
|
97
97
|
|
98
|
-
|
98
|
+
|
99
99
|
class << self
|
100
|
+
|
101
|
+
# Decode a UTF16 string to a ASCII string
|
102
|
+
# @param [String] str The string to convert
|
100
103
|
def decode_utf16le(str)
|
101
|
-
|
104
|
+
str.encode(Encoding::UTF_8, Encoding::UTF_16LE).force_encoding('UTF-8')
|
102
105
|
end
|
103
106
|
|
107
|
+
# Encodes a ASCII string to a UTF16 string
|
108
|
+
# @param [String] str The string to convert
|
109
|
+
# @note This implementation may seem stupid but the problem is that UTF16-LE and UTF-8 are incompatiable
|
110
|
+
# encodings. This library uses string contatination to build the packet bytes. The end result is that
|
111
|
+
# you can either marshal the encodings elsewhere of simply know that each time you call encode_utf16le
|
112
|
+
# the function will convert the string bytes to UTF-16LE and note the encoding as UTF-8 so that byte
|
113
|
+
# concatination works seamlessly.
|
104
114
|
def encode_utf16le(str)
|
105
|
-
|
115
|
+
str = str.force_encoding('UTF-8') if [::Encoding::ASCII_8BIT,::Encoding::US_ASCII].include?(str.encoding)
|
116
|
+
str.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
|
106
117
|
end
|
107
|
-
|
118
|
+
|
119
|
+
# Conver the value to a 64-Bit Little Endian Int
|
120
|
+
# @param [String] val The string to convert
|
108
121
|
def pack_int64le(val)
|
109
122
|
[val & 0x00000000ffffffff, val >> 32].pack("V2")
|
110
123
|
end
|
111
|
-
|
112
|
-
def swap16(str)
|
113
|
-
str.unpack("v*").pack("n*")
|
114
|
-
end
|
115
124
|
|
125
|
+
# Builds an array of strings that are 7 characters long
|
126
|
+
# @param [String] str The string to split
|
127
|
+
# @api private
|
116
128
|
def split7(str)
|
117
129
|
s = str.dup
|
118
130
|
until s.empty?
|
@@ -120,7 +132,10 @@ module Net #:nodoc:
|
|
120
132
|
end
|
121
133
|
ret
|
122
134
|
end
|
123
|
-
|
135
|
+
|
136
|
+
# Not sure what this is doing
|
137
|
+
# @param [String] str String to generate keys for
|
138
|
+
# @api private
|
124
139
|
def gen_keys(str)
|
125
140
|
split7(str).map{ |str7|
|
126
141
|
bits = split7(str7.unpack("B*")[0]).inject('')\
|
@@ -137,11 +152,16 @@ module Net #:nodoc:
|
|
137
152
|
}
|
138
153
|
end
|
139
154
|
|
155
|
+
# Generates a Lan Manager Hash
|
156
|
+
# @param [String] password The password to base the hash on
|
140
157
|
def lm_hash(password)
|
141
158
|
keys = gen_keys password.upcase.ljust(14, "\0")
|
142
159
|
apply_des(LM_MAGIC, keys).join
|
143
160
|
end
|
144
161
|
|
162
|
+
# Generate a NTLM Hash
|
163
|
+
# @param [String] password The password to base the hash on
|
164
|
+
# @option opt :unicode (false) Unicode encode the password
|
145
165
|
def ntlm_hash(password, opt = {})
|
146
166
|
pwd = password.dup
|
147
167
|
unless opt[:unicode]
|
@@ -150,6 +170,11 @@ module Net #:nodoc:
|
|
150
170
|
OpenSSL::Digest::MD4.digest pwd
|
151
171
|
end
|
152
172
|
|
173
|
+
# Generate a NTLMv2 Hash
|
174
|
+
# @param [String] user The username
|
175
|
+
# @param [String] password The password
|
176
|
+
# @param [String] target The domain or workstaiton to authenticate to
|
177
|
+
# @option opt :unicode (false) Unicode encode the domain
|
153
178
|
def ntlmv2_hash(user, password, target, opt={})
|
154
179
|
ntlmhash = ntlm_hash(password, opt)
|
155
180
|
userdomain = (user + target).upcase
|
@@ -159,7 +184,6 @@ module Net #:nodoc:
|
|
159
184
|
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
|
160
185
|
end
|
161
186
|
|
162
|
-
# responses
|
163
187
|
def lm_response(arg)
|
164
188
|
begin
|
165
189
|
hash = arg[:lm_hash]
|
@@ -254,6 +278,7 @@ module Net #:nodoc:
|
|
254
278
|
|
255
279
|
|
256
280
|
# base classes for primitives
|
281
|
+
# @private
|
257
282
|
class Field
|
258
283
|
attr_accessor :active, :value
|
259
284
|
|
@@ -297,7 +322,6 @@ module Net #:nodoc:
|
|
297
322
|
end
|
298
323
|
end
|
299
324
|
|
300
|
-
|
301
325
|
class Int16LE < Field
|
302
326
|
def initialize(opt)
|
303
327
|
super(opt)
|
@@ -361,34 +385,44 @@ module Net #:nodoc:
|
|
361
385
|
# base class of data structure
|
362
386
|
class FieldSet
|
363
387
|
class << FieldSet
|
364
|
-
def define(&block)
|
365
|
-
c = Class.new(self)
|
366
|
-
def c.inherited(subclass)
|
367
|
-
proto = @proto
|
368
|
-
subclass.instance_eval {
|
369
|
-
@proto = proto
|
370
|
-
}
|
371
|
-
end
|
372
|
-
c.module_eval(&block)
|
373
|
-
c
|
374
|
-
end
|
375
388
|
|
389
|
+
|
390
|
+
# @macro string_security_buffer
|
391
|
+
# @method $1
|
392
|
+
# @method $1=
|
393
|
+
# @return [String]
|
376
394
|
def string(name, opts)
|
377
395
|
add_field(name, String, opts)
|
378
396
|
end
|
379
397
|
|
398
|
+
# @macro int16le_security_buffer
|
399
|
+
# @method $1
|
400
|
+
# @method $1=
|
401
|
+
# @return [Int16LE]
|
380
402
|
def int16LE(name, opts)
|
381
403
|
add_field(name, Int16LE, opts)
|
382
404
|
end
|
383
405
|
|
406
|
+
# @macro int32le_security_buffer
|
407
|
+
# @method $1
|
408
|
+
# @method $1=
|
409
|
+
# @return [Int32LE]
|
384
410
|
def int32LE(name, opts)
|
385
411
|
add_field(name, Int32LE, opts)
|
386
412
|
end
|
387
413
|
|
414
|
+
# @macro int64le_security_buffer
|
415
|
+
# @method $1
|
416
|
+
# @method $1=
|
417
|
+
# @return [Int64]
|
388
418
|
def int64LE(name, opts)
|
389
419
|
add_field(name, Int64LE, opts)
|
390
420
|
end
|
391
421
|
|
422
|
+
# @macro security_buffer
|
423
|
+
# @method $1
|
424
|
+
# @method $1=
|
425
|
+
# @return [SecurityBuffer]
|
392
426
|
def security_buffer(name, opts)
|
393
427
|
add_field(name, SecurityBuffer, opts)
|
394
428
|
end
|
@@ -466,8 +500,7 @@ module Net #:nodoc:
|
|
466
500
|
end
|
467
501
|
end
|
468
502
|
|
469
|
-
|
470
|
-
Blob = FieldSet.define {
|
503
|
+
class Blob < FieldSet
|
471
504
|
int32LE :blob_signature, {:value => BLOB_SIGN}
|
472
505
|
int32LE :reserved, {:value => 0}
|
473
506
|
int64LE :timestamp, {:value => 0}
|
@@ -475,15 +508,14 @@ module Net #:nodoc:
|
|
475
508
|
int32LE :unknown1, {:value => 0}
|
476
509
|
string :target_info, {:value => "", :size => 0}
|
477
510
|
int32LE :unknown2, {:value => 0}
|
478
|
-
|
511
|
+
end
|
512
|
+
|
513
|
+
class SecurityBuffer < FieldSet
|
479
514
|
|
480
|
-
SecurityBuffer = FieldSet.define {
|
481
515
|
int16LE :length, {:value => 0}
|
482
516
|
int16LE :allocated, {:value => 0}
|
483
517
|
int32LE :offset, {:value => 0}
|
484
|
-
}
|
485
518
|
|
486
|
-
class SecurityBuffer
|
487
519
|
attr_accessor :active
|
488
520
|
def initialize(opts)
|
489
521
|
super()
|
@@ -519,7 +551,8 @@ module Net #:nodoc:
|
|
519
551
|
@active ? @value.size : 0
|
520
552
|
end
|
521
553
|
end
|
522
|
-
|
554
|
+
|
555
|
+
# @private false
|
523
556
|
class Message < FieldSet
|
524
557
|
class << Message
|
525
558
|
def parse(str)
|
@@ -579,8 +612,6 @@ module Net #:nodoc:
|
|
579
612
|
end
|
580
613
|
|
581
614
|
|
582
|
-
private
|
583
|
-
|
584
615
|
def security_buffers
|
585
616
|
@alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
|
586
617
|
end
|
@@ -597,23 +628,25 @@ module Net #:nodoc:
|
|
597
628
|
end
|
598
629
|
|
599
630
|
# sub class definitions
|
600
|
-
|
601
|
-
Type0 = Message.define {
|
631
|
+
class Type0 < Message
|
602
632
|
string :sign, {:size => 8, :value => SSP_SIGN}
|
603
633
|
int32LE :type, {:value => 0}
|
604
|
-
|
605
|
-
|
606
|
-
|
634
|
+
end
|
635
|
+
|
636
|
+
# @private false
|
637
|
+
class Type1 < Message
|
638
|
+
|
607
639
|
string :sign, {:size => 8, :value => SSP_SIGN}
|
608
640
|
int32LE :type, {:value => 1}
|
609
641
|
int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
|
610
642
|
security_buffer :domain, {:value => ""}
|
611
643
|
security_buffer :workstation, {:value => Socket.gethostname }
|
612
644
|
string :padding, {:size => 0, :value => "", :active => false }
|
613
|
-
}
|
614
645
|
|
615
|
-
class Type1
|
616
646
|
class << Type1
|
647
|
+
# Parses a Type 1 Message
|
648
|
+
# @param [String] str A string containing Type 1 data
|
649
|
+
# @return [Type1] The parsed Type 1 message
|
617
650
|
def parse(str)
|
618
651
|
t = new
|
619
652
|
t.parse(str)
|
@@ -621,6 +654,7 @@ module Net #:nodoc:
|
|
621
654
|
end
|
622
655
|
end
|
623
656
|
|
657
|
+
# @!visibility private
|
624
658
|
def parse(str)
|
625
659
|
super(str)
|
626
660
|
enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
|
@@ -633,7 +667,10 @@ module Net #:nodoc:
|
|
633
667
|
end
|
634
668
|
end
|
635
669
|
|
636
|
-
|
670
|
+
|
671
|
+
# @private false
|
672
|
+
class Type2 < Message
|
673
|
+
|
637
674
|
string :sign, {:size => 8, :value => SSP_SIGN}
|
638
675
|
int32LE :type, {:value => 2}
|
639
676
|
security_buffer :target_name, {:size => 0, :value => ""}
|
@@ -642,17 +679,19 @@ module Net #:nodoc:
|
|
642
679
|
int64LE :context, {:value => 0, :active => false}
|
643
680
|
security_buffer :target_info, {:value => "", :active => false}
|
644
681
|
string :padding, {:size => 0, :value => "", :active => false }
|
645
|
-
|
646
|
-
|
647
|
-
class Type2
|
682
|
+
|
648
683
|
class << Type2
|
684
|
+
# Parse a Type 2 packet
|
685
|
+
# @param [String] str A string containing Type 2 data
|
686
|
+
# @return [Type2]
|
649
687
|
def parse(str)
|
650
688
|
t = new
|
651
689
|
t.parse(str)
|
652
690
|
t
|
653
691
|
end
|
654
692
|
end
|
655
|
-
|
693
|
+
|
694
|
+
# @!visibility private
|
656
695
|
def parse(str)
|
657
696
|
super(str)
|
658
697
|
if has_flag?(:TARGET_INFO)
|
@@ -665,7 +704,16 @@ module Net #:nodoc:
|
|
665
704
|
super(str)
|
666
705
|
end
|
667
706
|
end
|
668
|
-
|
707
|
+
|
708
|
+
# Generates a Type 3 response based on the Type 2 Information
|
709
|
+
# @return [Type3]
|
710
|
+
# @option arg [String] :username The username to authenticate with
|
711
|
+
# @option arg [String] :password The user's password
|
712
|
+
# @option arg [String] :domain ('') The domain to authenticate to
|
713
|
+
# @option opt [String] :workstation (Socket.gethostname) The name of the calling workstation
|
714
|
+
# @option opt [Boolean] :use_default_target (False) Use the domain supplied by the server in the Type 2 packet
|
715
|
+
# @note An empty :domain option authenticates to the local machine.
|
716
|
+
# @note The :use_default_target has presidence over the :domain option
|
669
717
|
def response(arg, opt = {})
|
670
718
|
usr = arg[:user]
|
671
719
|
pwd = arg[:password]
|
@@ -677,7 +725,7 @@ module Net #:nodoc:
|
|
677
725
|
if opt[:workstation]
|
678
726
|
ws = opt[:workstation]
|
679
727
|
else
|
680
|
-
ws =
|
728
|
+
ws = Socket.gethostname
|
681
729
|
end
|
682
730
|
|
683
731
|
if opt[:client_challenge]
|
@@ -692,6 +740,7 @@ module Net #:nodoc:
|
|
692
740
|
usr = NTLM::decode_utf16le(usr)
|
693
741
|
pwd = NTLM::decode_utf16le(pwd)
|
694
742
|
ws = NTLM::decode_utf16le(ws)
|
743
|
+
domain = NTLM::decode_utf16le(domain)
|
695
744
|
opt[:unicode] = false
|
696
745
|
end
|
697
746
|
|
@@ -699,9 +748,14 @@ module Net #:nodoc:
|
|
699
748
|
usr = NTLM::encode_utf16le(usr)
|
700
749
|
pwd = NTLM::encode_utf16le(pwd)
|
701
750
|
ws = NTLM::encode_utf16le(ws)
|
751
|
+
domain = NTLM::encode_utf16le(domain)
|
702
752
|
opt[:unicode] = true
|
703
753
|
end
|
704
754
|
|
755
|
+
if opt[:use_default_target]
|
756
|
+
domain = self.target_name
|
757
|
+
end
|
758
|
+
|
705
759
|
ti = self.target_info
|
706
760
|
|
707
761
|
chal = self[:challenge].serialize
|
@@ -729,8 +783,9 @@ module Net #:nodoc:
|
|
729
783
|
end
|
730
784
|
end
|
731
785
|
|
732
|
-
|
733
|
-
Type3
|
786
|
+
# @private false
|
787
|
+
class Type3 < Message
|
788
|
+
|
734
789
|
string :sign, {:size => 8, :value => SSP_SIGN}
|
735
790
|
int32LE :type, {:value => 3}
|
736
791
|
security_buffer :lm_response, {:value => ""}
|
@@ -740,16 +795,26 @@ module Net #:nodoc:
|
|
740
795
|
security_buffer :workstation, {:value => ""}
|
741
796
|
security_buffer :session_key, {:value => "", :active => false }
|
742
797
|
int64LE :flag, {:value => 0, :active => false }
|
743
|
-
|
744
|
-
|
745
|
-
class Type3
|
798
|
+
|
746
799
|
class << Type3
|
800
|
+
# Parse a Type 3 packet
|
801
|
+
# @param [String] str A string containing Type 3 data
|
802
|
+
# @return [Type2]
|
747
803
|
def parse(str)
|
748
804
|
t = new
|
749
805
|
t.parse(str)
|
750
806
|
t
|
751
807
|
end
|
752
|
-
|
808
|
+
|
809
|
+
# Builds a Type 3 packet
|
810
|
+
# @note All options must be properly encoded with either unicode or oem encoding
|
811
|
+
# @return [Type3]
|
812
|
+
# @option arg [String] :lm_response The LM hash
|
813
|
+
# @option arg [String] :ntlm_response The NTLM hash
|
814
|
+
# @option arg [String] :domain The domain to authenticate to
|
815
|
+
# @option arg [String] :workstation The name of the calling workstation
|
816
|
+
# @option arg [String] :session_key The session key
|
817
|
+
# @option arg [Integer] :flag Flags for the packet
|
753
818
|
def create(arg, opt ={})
|
754
819
|
t = new
|
755
820
|
t.lm_response = arg[:lm_response]
|
@@ -759,14 +824,13 @@ module Net #:nodoc:
|
|
759
824
|
|
760
825
|
if arg[:workstation]
|
761
826
|
t.workstation = arg[:workstation]
|
762
|
-
else
|
763
|
-
t.workstation = NTLM.encode_utf16le(Socket.gethostname)
|
764
827
|
end
|
765
828
|
|
766
829
|
if arg[:session_key]
|
767
830
|
t.enable(:session_key)
|
768
831
|
t.session_key = arg[session_key]
|
769
832
|
end
|
833
|
+
|
770
834
|
if arg[:flag]
|
771
835
|
t.enable(:session_key)
|
772
836
|
t.enable(:flag)
|
data/rubyntlm.gemspec
CHANGED
@@ -16,7 +16,9 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
17
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
18
|
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.required_ruby_version = '1.9.2'
|
19
21
|
|
20
|
-
s.add_development_dependency "bundler", "~> 1.3"
|
21
22
|
s.add_development_dependency "rake"
|
23
|
+
s.add_development_dependency "rspec"
|
22
24
|
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
$:.unshift(File.expand_path(File.dirname(__FILE__) << '../lib'))
|
3
|
+
require 'net/ntlm'
|
4
|
+
|
5
|
+
describe Net::NTLM::Message do
|
6
|
+
let(:type1_packet) {"TlRMTVNTUAABAAAAB4IIAAAAAAAgAAAAAAAAACAAAAA="}
|
7
|
+
let(:type2_packet) {"TlRMTVNTUAACAAAAHAAcADgAAAAFgooCJ+UA1//+ZM4AAAAAAAAAAJAAkABUAAAABgGxHQAAAA9WAEEARwBSAEEATgBUAC0AMgAwADAAOABSADIAAgAcAFYAQQBHAFIAQQBOAFQALQAyADAAMAA4AFIAMgABABwAVgBBAEcAUgBBAE4AVAAtADIAMAAwADgAUgAyAAQAHAB2AGEAZwByAGEAbgB0AC0AMgAwADAAOABSADIAAwAcAHYAYQBnAHIAYQBuAHQALQAyADAAMAA4AFIAMgAHAAgAZBMdFHQnzgEAAAAA"}
|
8
|
+
let(:type3_packet) {"TlRMTVNTUAADAAAAGAAYAEQAAADAAMAAXAAAAAAAAAAcAQAADgAOABwBAAAUABQAKgEAAAAAAAA+AQAABYKKAgAAAADVS27TfQGmWxSSbXmolTUQyxJmD8ISQuBKKHFKC8GksUZISYc8Ps9RAQEAAAAAAAAANasTdCfOAcsSZg/CEkLgAAAAAAIAHABWAEEARwBSAEEATgBUAC0AMgAwADAAOABSADIAAQAcAFYAQQBHAFIAQQBOAFQALQAyADAAMAA4AFIAMgAEABwAdgBhAGcAcgBhAG4AdAAtADIAMAAwADgAUgAyAAMAHAB2AGEAZwByAGEAbgB0AC0AMgAwADAAOABSADIABwAIAGQTHRR0J84BAAAAAAAAAAB2AGEAZwByAGEAbgB0AGsAbwBiAGUALgBsAG8AYwBhAGwA"}
|
9
|
+
describe Net::NTLM::Message::Type1 do
|
10
|
+
it 'should deserialize' do
|
11
|
+
t1 = Net::NTLM::Message.decode64(type1_packet)
|
12
|
+
t1.class.should == Net::NTLM::Message::Type1
|
13
|
+
t1.domain.should == ''
|
14
|
+
t1.flag.should == 557575
|
15
|
+
t1.padding.should == ''
|
16
|
+
t1.sign.should == "NTLMSSP\0"
|
17
|
+
t1.type.should == 1
|
18
|
+
t1.workstation.should == ''
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should serialize' do
|
22
|
+
t1 = Net::NTLM::Message::Type1.new
|
23
|
+
t1.workstation = ''
|
24
|
+
t1.encode64.should == type1_packet
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe Net::NTLM::Message::Type2 do
|
29
|
+
it 'should deserialize' do
|
30
|
+
t2 = Net::NTLM::Message.decode64(type2_packet)
|
31
|
+
t2.class.should == Net::NTLM::Message::Type2
|
32
|
+
t2.challenge.should == 14872292244261496103
|
33
|
+
t2.context.should == 0
|
34
|
+
t2.flag.should == 42631685
|
35
|
+
t2.padding.should == ("\x06\x01\xB1\x1D\0\0\0\x0F".force_encoding('ASCII-8BIT'))
|
36
|
+
t2.sign.should == "NTLMSSP\0"
|
37
|
+
Net::NTLM.decode_utf16le(t2.target_info).should == "\u0002\u001CVAGRANT-2008R2\u0001\u001CVAGRANT-2008R2\u0004\u001Cvagrant-2008R2\u0003\u001Cvagrant-2008R2\a\b፤ᐝ❴ǎ\0\0"
|
38
|
+
Net::NTLM.decode_utf16le(t2.target_name).should == "VAGRANT-2008R2"
|
39
|
+
t2.type.should == 2
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should serialize' do
|
43
|
+
source = Net::NTLM::Message.decode64(type2_packet)
|
44
|
+
|
45
|
+
t2 = Net::NTLM::Message::Type2.new
|
46
|
+
t2.challenge = source.challenge
|
47
|
+
t2.context = source.context
|
48
|
+
t2.flag = source.flag
|
49
|
+
t2.padding = source.padding
|
50
|
+
t2.sign = source.sign
|
51
|
+
t2.target_info = source.target_info
|
52
|
+
t2.target_name = source.target_name
|
53
|
+
t2.type = source.type
|
54
|
+
t2.enable(:context)
|
55
|
+
t2.enable(:target_info)
|
56
|
+
t2.enable(:padding)
|
57
|
+
|
58
|
+
t2.encode64.should == type2_packet
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should generate a type 3 response' do
|
62
|
+
t2 = Net::NTLM::Message.decode64(type2_packet)
|
63
|
+
|
64
|
+
type3_known = Net::NTLM::Message.decode64(type3_packet)
|
65
|
+
type3_known.flag = 0x028a8205
|
66
|
+
type3_known.enable(:session_key)
|
67
|
+
type3_known.enable(:flag)
|
68
|
+
|
69
|
+
t3 = t2.response({:user => 'vagrant', :password => 'vagrant', :domain => ''}, {:ntlmv2 => true, :workstation => 'kobe.local'})
|
70
|
+
t3.domain.should == type3_known.domain
|
71
|
+
t3.flag.should == type3_known.flag
|
72
|
+
t3.sign.should == "NTLMSSP\0"
|
73
|
+
t3.workstation.should == "k\0o\0b\0e\0.\0l\0o\0c\0a\0l\0"
|
74
|
+
t3.user.should == "v\0a\0g\0r\0a\0n\0t\0"
|
75
|
+
t3.session_key.should == ''
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
describe Net::NTLM do
|
82
|
+
let(:passwd) {"SecREt01"}
|
83
|
+
let(:user) {"user"}
|
84
|
+
let(:domain) {"domain"}
|
85
|
+
let(:challenge) {["0123456789abcdef"].pack("H*")}
|
86
|
+
let(:client_ch) {["ffffff0011223344"].pack("H*")}
|
87
|
+
let(:timestamp) {1055844000}
|
88
|
+
let(:trgt_info) {[
|
89
|
+
"02000c0044004f004d00410049004e00" +
|
90
|
+
"01000c00530045005200560045005200" +
|
91
|
+
"0400140064006f006d00610069006e00" +
|
92
|
+
"2e0063006f006d000300220073006500" +
|
93
|
+
"72007600650072002e0064006f006d00" +
|
94
|
+
"610069006e002e0063006f006d000000" +
|
95
|
+
"0000"
|
96
|
+
].pack("H*")}
|
97
|
+
|
98
|
+
it 'should generate an lm_hash' do
|
99
|
+
Net::NTLM::lm_hash(passwd).should == ["ff3750bcc2b22412c2265b23734e0dac"].pack("H*")
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should generate an ntlm_hash' do
|
103
|
+
Net::NTLM::ntlm_hash(passwd).should == ["cd06ca7c7e10c99b1d33b7485a2ed808"].pack("H*")
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should generate an ntlmv2_hash' do
|
107
|
+
Net::NTLM::ntlmv2_hash(user, passwd, domain).should == ["04b8e0ba74289cc540826bab1dee63ae"].pack("H*")
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should generate an lm_response' do
|
111
|
+
Net::NTLM::lm_response(
|
112
|
+
{
|
113
|
+
:lm_hash => Net::NTLM::lm_hash(passwd),
|
114
|
+
:challenge => challenge
|
115
|
+
}
|
116
|
+
).should == ["c337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56"].pack("H*")
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should generate an ntlm_response' do
|
120
|
+
ntlm_hash = Net::NTLM::ntlm_hash(passwd)
|
121
|
+
Net::NTLM::ntlm_response(
|
122
|
+
{
|
123
|
+
:ntlm_hash => ntlm_hash,
|
124
|
+
:challenge => challenge
|
125
|
+
}
|
126
|
+
).should == ["25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6"].pack("H*")
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should generate a lvm2_response' do
|
130
|
+
Net::NTLM::lmv2_response(
|
131
|
+
{
|
132
|
+
:ntlmv2_hash => Net::NTLM::ntlmv2_hash(user, passwd, domain),
|
133
|
+
:challenge => challenge
|
134
|
+
},
|
135
|
+
{ :client_challenge => client_ch }
|
136
|
+
).should == ["d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"].pack("H*")
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should generate a ntlmv2_response' do
|
140
|
+
Net::NTLM::ntlmv2_response(
|
141
|
+
{
|
142
|
+
:ntlmv2_hash => Net::NTLM::ntlmv2_hash(user, passwd, domain),
|
143
|
+
:challenge => challenge,
|
144
|
+
:target_info => trgt_info
|
145
|
+
},
|
146
|
+
{
|
147
|
+
:timestamp => timestamp,
|
148
|
+
:client_challenge => client_ch
|
149
|
+
}
|
150
|
+
).should == [
|
151
|
+
"cbabbca713eb795d04c97abc01ee4983" +
|
152
|
+
"01010000000000000090d336b734c301" +
|
153
|
+
"ffffff00112233440000000002000c00" +
|
154
|
+
"44004f004d00410049004e0001000c00" +
|
155
|
+
"53004500520056004500520004001400" +
|
156
|
+
"64006f006d00610069006e002e006300" +
|
157
|
+
"6f006d00030022007300650072007600" +
|
158
|
+
"650072002e0064006f006d0061006900" +
|
159
|
+
"6e002e0063006f006d00000000000000" +
|
160
|
+
"0000"
|
161
|
+
].pack("H*")
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should generate a ntlm2_session' do
|
165
|
+
session = Net::NTLM::ntlm2_session(
|
166
|
+
{
|
167
|
+
:ntlm_hash => Net::NTLM::ntlm_hash(passwd),
|
168
|
+
:challenge => challenge
|
169
|
+
},
|
170
|
+
{ :client_challenge => client_ch }
|
171
|
+
)
|
172
|
+
session[0].should == ["ffffff001122334400000000000000000000000000000000"].pack("H*")
|
173
|
+
session[1].should == ["10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455"].pack("H*")
|
174
|
+
end
|
175
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyntlm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,26 +10,26 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-03-
|
13
|
+
date: 2013-03-25 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
16
|
+
name: rake
|
17
17
|
requirement: !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
|
-
- -
|
20
|
+
- - '>='
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
22
|
+
version: '0'
|
23
23
|
type: :development
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
none: false
|
27
27
|
requirements:
|
28
|
-
- -
|
28
|
+
- - '>='
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version: '
|
30
|
+
version: '0'
|
31
31
|
- !ruby/object:Gem::Dependency
|
32
|
-
name:
|
32
|
+
name: rspec
|
33
33
|
requirement: !ruby/object:Gem::Requirement
|
34
34
|
none: false
|
35
35
|
requirements:
|
@@ -52,6 +52,7 @@ executables: []
|
|
52
52
|
extensions: []
|
53
53
|
extra_rdoc_files: []
|
54
54
|
files:
|
55
|
+
- .gitignore
|
55
56
|
- .travis.yml
|
56
57
|
- CHANGELOG.md
|
57
58
|
- Gemfile
|
@@ -64,7 +65,7 @@ files:
|
|
64
65
|
- lib/net/ntlm.rb
|
65
66
|
- lib/rubyntlm.rb
|
66
67
|
- rubyntlm.gemspec
|
67
|
-
-
|
68
|
+
- spec/unit/ntlm_spec.rb
|
68
69
|
homepage: https://github.com/winrb/rubyntlm
|
69
70
|
licenses: []
|
70
71
|
post_install_message:
|
@@ -74,12 +75,9 @@ require_paths:
|
|
74
75
|
required_ruby_version: !ruby/object:Gem::Requirement
|
75
76
|
none: false
|
76
77
|
requirements:
|
77
|
-
- - '
|
78
|
+
- - '='
|
78
79
|
- !ruby/object:Gem::Version
|
79
|
-
version:
|
80
|
-
segments:
|
81
|
-
- 0
|
82
|
-
hash: -254558294721854043
|
80
|
+
version: 1.9.2
|
83
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
82
|
none: false
|
85
83
|
requirements:
|
@@ -88,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
86
|
version: '0'
|
89
87
|
segments:
|
90
88
|
- 0
|
91
|
-
hash: -
|
89
|
+
hash: -473842116073612192
|
92
90
|
requirements: []
|
93
91
|
rubyforge_project:
|
94
92
|
rubygems_version: 1.8.25
|
@@ -96,4 +94,4 @@ signing_key:
|
|
96
94
|
specification_version: 3
|
97
95
|
summary: Ruby/NTLM library.
|
98
96
|
test_files:
|
99
|
-
-
|
97
|
+
- spec/unit/ntlm_spec.rb
|
data/test/function_test.rb
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
# $Id$
|
2
|
-
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
3
|
-
require 'test/unit'
|
4
|
-
require 'net/ntlm'
|
5
|
-
|
6
|
-
class FunctionTest < Test::Unit::TestCase #:nodoc:
|
7
|
-
def setup
|
8
|
-
@passwd = "SecREt01"
|
9
|
-
@user = "user"
|
10
|
-
@domain = "domain"
|
11
|
-
@challenge = ["0123456789abcdef"].pack("H*")
|
12
|
-
@client_ch = ["ffffff0011223344"].pack("H*")
|
13
|
-
@timestamp = 1055844000
|
14
|
-
@trgt_info = [
|
15
|
-
"02000c0044004f004d00410049004e00" +
|
16
|
-
"01000c00530045005200560045005200" +
|
17
|
-
"0400140064006f006d00610069006e00" +
|
18
|
-
"2e0063006f006d000300220073006500" +
|
19
|
-
"72007600650072002e0064006f006d00" +
|
20
|
-
"610069006e002e0063006f006d000000" +
|
21
|
-
"0000"
|
22
|
-
].pack("H*")
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_lm_hash
|
26
|
-
ahash = ["ff3750bcc2b22412c2265b23734e0dac"].pack("H*")
|
27
|
-
assert_equal ahash, Net::NTLM::lm_hash(@passwd)
|
28
|
-
end
|
29
|
-
|
30
|
-
def test_ntlm_hash
|
31
|
-
ahash = ["cd06ca7c7e10c99b1d33b7485a2ed808"].pack("H*")
|
32
|
-
assert_equal ahash, Net::NTLM::ntlm_hash(@passwd)
|
33
|
-
end
|
34
|
-
|
35
|
-
def test_ntlmv2_hash
|
36
|
-
ahash = ["04b8e0ba74289cc540826bab1dee63ae"].pack("H*")
|
37
|
-
assert_equal ahash, Net::NTLM::ntlmv2_hash(@user, @passwd, @domain)
|
38
|
-
end
|
39
|
-
|
40
|
-
def test_lm_response
|
41
|
-
ares = ["c337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56"].pack("H*")
|
42
|
-
assert_equal ares, Net::NTLM::lm_response(
|
43
|
-
{
|
44
|
-
:lm_hash => Net::NTLM::lm_hash(@passwd),
|
45
|
-
:challenge => @challenge
|
46
|
-
}
|
47
|
-
)
|
48
|
-
end
|
49
|
-
|
50
|
-
def test_ntlm_response
|
51
|
-
ares = ["25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6"].pack("H*")
|
52
|
-
ntlm_hash = Net::NTLM::ntlm_hash(@passwd)
|
53
|
-
assert_equal ares, Net::NTLM::ntlm_response(
|
54
|
-
{
|
55
|
-
:ntlm_hash => ntlm_hash,
|
56
|
-
:challenge => @challenge
|
57
|
-
}
|
58
|
-
)
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_lmv2_response
|
62
|
-
ares = ["d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"].pack("H*")
|
63
|
-
assert_equal ares, Net::NTLM::lmv2_response(
|
64
|
-
{
|
65
|
-
:ntlmv2_hash => Net::NTLM::ntlmv2_hash(@user, @passwd, @domain),
|
66
|
-
:challenge => @challenge
|
67
|
-
},
|
68
|
-
{ :client_challenge => @client_ch }
|
69
|
-
)
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_ntlmv2_response
|
73
|
-
ares = [
|
74
|
-
"cbabbca713eb795d04c97abc01ee4983" +
|
75
|
-
"01010000000000000090d336b734c301" +
|
76
|
-
"ffffff00112233440000000002000c00" +
|
77
|
-
"44004f004d00410049004e0001000c00" +
|
78
|
-
"53004500520056004500520004001400" +
|
79
|
-
"64006f006d00610069006e002e006300" +
|
80
|
-
"6f006d00030022007300650072007600" +
|
81
|
-
"650072002e0064006f006d0061006900" +
|
82
|
-
"6e002e0063006f006d00000000000000" +
|
83
|
-
"0000"
|
84
|
-
].pack("H*")
|
85
|
-
assert_equal ares, Net::NTLM::ntlmv2_response(
|
86
|
-
{
|
87
|
-
:ntlmv2_hash => Net::NTLM::ntlmv2_hash(@user, @passwd, @domain),
|
88
|
-
:challenge => @challenge,
|
89
|
-
:target_info => @trgt_info
|
90
|
-
},
|
91
|
-
{
|
92
|
-
:timestamp => @timestamp,
|
93
|
-
:client_challenge => @client_ch
|
94
|
-
}
|
95
|
-
)
|
96
|
-
end
|
97
|
-
|
98
|
-
def test_ntlm2_session
|
99
|
-
acha = ["ffffff001122334400000000000000000000000000000000"].pack("H*")
|
100
|
-
ares = ["10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455"].pack("H*")
|
101
|
-
session = Net::NTLM::ntlm2_session(
|
102
|
-
{
|
103
|
-
:ntlm_hash => Net::NTLM::ntlm_hash(@passwd),
|
104
|
-
:challenge => @challenge
|
105
|
-
},
|
106
|
-
{ :client_challenge => @client_ch }
|
107
|
-
)
|
108
|
-
assert_equal acha, session[0]
|
109
|
-
assert_equal ares, session[1]
|
110
|
-
end
|
111
|
-
end
|