rubyntlm 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/
|
3
|
+
[![Build Status](https://travis-ci.org/WinRb/rubyntlm.png)](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
|