rubyntlm 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/.rspec +2 -3
- data/README.md +9 -2
- data/Rakefile +8 -0
- data/examples/http.rb +1 -1
- data/lib/net/ntlm.rb +10 -9
- data/lib/net/ntlm/blob.rb +17 -6
- data/lib/net/ntlm/client.rb +61 -0
- data/lib/net/ntlm/client/session.rb +223 -0
- data/lib/net/ntlm/encode_util.rb +2 -2
- data/lib/net/ntlm/field_set.rb +9 -5
- data/lib/net/ntlm/message.rb +23 -9
- data/lib/net/ntlm/message/type1.rb +1 -26
- data/lib/net/ntlm/message/type2.rb +11 -37
- data/lib/net/ntlm/message/type3.rb +77 -14
- data/lib/net/ntlm/version.rb +1 -1
- data/rubyntlm.gemspec +3 -2
- data/spec/lib/net/ntlm/blob_spec.rb +1 -1
- data/spec/lib/net/ntlm/client/session_spec.rb +68 -0
- data/spec/lib/net/ntlm/client_spec.rb +64 -0
- data/spec/lib/net/ntlm/encode_util_spec.rb +3 -3
- data/spec/lib/net/ntlm/field_set_spec.rb +4 -4
- data/spec/lib/net/ntlm/field_spec.rb +5 -5
- data/spec/lib/net/ntlm/message/type1_spec.rb +99 -10
- data/spec/lib/net/ntlm/message/type2_spec.rb +68 -24
- data/spec/lib/net/ntlm/message/type3_spec.rb +207 -2
- data/spec/lib/net/ntlm/security_buffer_spec.rb +8 -8
- data/spec/lib/net/ntlm/string_spec.rb +14 -14
- data/spec/lib/net/ntlm/version_spec.rb +7 -6
- data/spec/lib/net/ntlm_spec.rb +20 -22
- data/spec/spec_helper.rb +1 -2
- data/spec/support/shared/examples/net/ntlm/field_shared.rb +3 -3
- data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +34 -34
- data/spec/support/shared/examples/net/ntlm/int_shared.rb +8 -8
- data/spec/support/shared/examples/net/ntlm/message_shared.rb +3 -3
- metadata +36 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33cb0ea71a4b1d8149a2a8bf39c5f24bd2752d5b
|
4
|
+
data.tar.gz: 032e16ead1a05ddcf0268d79c4ffff2ff631bfcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c276677b10f3f2b7fa35817fce82e1cfd5012298c752fbfc4576f0b4d45d339a5e6589fb547c16aa6609cb4174f2e9ac396c0f49ad7e7267f5b68b20f6e1fa47
|
7
|
+
data.tar.gz: 40e3423ad351782e571611b32704b583c0b75a06a2b3acedc62dc9c6e916b0bc3356fe51d1060dd3cda0ef330b175ad108623625b3f7a6f6eb7728f66d03216e
|
data/.rspec
CHANGED
@@ -1,3 +1,2 @@
|
|
1
|
-
--format
|
2
|
-
--
|
3
|
-
--drb
|
1
|
+
--format documentation
|
2
|
+
--color
|
data/README.md
CHANGED
@@ -6,19 +6,26 @@ Ruby/NTLM provides message creator and parser for the NTLM authentication.
|
|
6
6
|
|
7
7
|
__100% Ruby__
|
8
8
|
|
9
|
+
How to install
|
10
|
+
--------------
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
require 'rubyntlm'
|
14
|
+
```
|
15
|
+
|
9
16
|
Simple Example
|
10
17
|
--------------
|
11
18
|
|
12
19
|
### Creating NTLM Type 1 message
|
13
20
|
|
14
21
|
```ruby
|
15
|
-
t1 = NTLM::Message::Type1.new()
|
22
|
+
t1 = Net::NTLM::Message::Type1.new()
|
16
23
|
```
|
17
24
|
|
18
25
|
### Parsing NTLM Type 2 message from server
|
19
26
|
|
20
27
|
```ruby
|
21
|
-
t2 = NTLM::Message.parse(message_from_server)
|
28
|
+
t2 = Net::NTLM::Message.parse(message_from_server)
|
22
29
|
```
|
23
30
|
|
24
31
|
### Creating NTLM Type 3 message
|
data/Rakefile
CHANGED
data/examples/http.rb
CHANGED
@@ -48,7 +48,7 @@ def main
|
|
48
48
|
|
49
49
|
unless $user and $passwd
|
50
50
|
target = t2.target_name
|
51
|
-
target = Net::NTLM::decode_utf16le(target) if t2.has_flag?(:UNICODE)
|
51
|
+
target = Net::NTLM::EncodeUtil.decode_utf16le(target) if t2.has_flag?(:UNICODE)
|
52
52
|
puts "Target: #{target}"
|
53
53
|
print "User name: "
|
54
54
|
($user = $stdin.readline).chomp!
|
data/lib/net/ntlm.rb
CHANGED
@@ -57,11 +57,9 @@ require 'net/ntlm/message/type1'
|
|
57
57
|
require 'net/ntlm/message/type2'
|
58
58
|
require 'net/ntlm/message/type3'
|
59
59
|
|
60
|
-
|
61
60
|
require 'net/ntlm/encode_util'
|
62
61
|
|
63
|
-
|
64
|
-
|
62
|
+
require 'net/ntlm/client'
|
65
63
|
|
66
64
|
module Net
|
67
65
|
module NTLM
|
@@ -102,10 +100,11 @@ module Net
|
|
102
100
|
end
|
103
101
|
|
104
102
|
def apply_des(plain, keys)
|
105
|
-
dec = OpenSSL::Cipher::
|
103
|
+
dec = OpenSSL::Cipher::Cipher.new("des-cbc")
|
104
|
+
dec.padding = 0
|
106
105
|
keys.map {|k|
|
107
106
|
dec.key = k
|
108
|
-
dec.encrypt.update(plain)
|
107
|
+
dec.encrypt.update(plain) + dec.final
|
109
108
|
}
|
110
109
|
end
|
111
110
|
|
@@ -134,7 +133,7 @@ module Net
|
|
134
133
|
# @option opt :unicode (false) Unicode encode the domain
|
135
134
|
def ntlmv2_hash(user, password, target, opt={})
|
136
135
|
ntlmhash = ntlm_hash(password, opt)
|
137
|
-
userdomain =
|
136
|
+
userdomain = user.upcase + target
|
138
137
|
unless opt[:unicode]
|
139
138
|
userdomain = EncodeUtil.encode_utf16le(userdomain)
|
140
139
|
end
|
@@ -184,7 +183,7 @@ module Net
|
|
184
183
|
ts = Time.now.to_i
|
185
184
|
end
|
186
185
|
# epoch -> milsec from Jan 1, 1601
|
187
|
-
ts =
|
186
|
+
ts = 10_000_000 * (ts + TIME_OFFSET)
|
188
187
|
|
189
188
|
blob = Blob.new
|
190
189
|
blob.timestamp = ts
|
@@ -192,6 +191,7 @@ module Net
|
|
192
191
|
blob.target_info = ti
|
193
192
|
|
194
193
|
bb = blob.serialize
|
194
|
+
|
195
195
|
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
|
196
196
|
end
|
197
197
|
|
@@ -218,15 +218,16 @@ module Net
|
|
218
218
|
rescue
|
219
219
|
raise ArgumentError
|
220
220
|
end
|
221
|
+
chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
|
221
222
|
|
222
223
|
if opt[:client_challenge]
|
223
|
-
cc
|
224
|
+
cc = opt[:client_challenge]
|
224
225
|
else
|
225
226
|
cc = rand(MAX64)
|
226
227
|
end
|
227
228
|
cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
|
228
229
|
|
229
|
-
keys = gen_keys
|
230
|
+
keys = gen_keys(passwd_hash.ljust(21, "\0"))
|
230
231
|
session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
|
231
232
|
response = apply_des(session_hash, keys).join
|
232
233
|
[cc.ljust(24, "\0"), response]
|
data/lib/net/ntlm/blob.rb
CHANGED
@@ -4,14 +4,25 @@ module Net
|
|
4
4
|
BLOB_SIGN = 0x00000101
|
5
5
|
|
6
6
|
class Blob < FieldSet
|
7
|
-
int32LE :blob_signature,
|
8
|
-
int32LE :reserved,
|
7
|
+
int32LE :blob_signature, {:value => BLOB_SIGN}
|
8
|
+
int32LE :reserved, {:value => 0}
|
9
9
|
int64LE :timestamp, {:value => 0}
|
10
10
|
string :challenge, {:value => "", :size => 8}
|
11
|
-
int32LE :unknown1,
|
12
|
-
string :target_info,
|
13
|
-
int32LE :unknown2,
|
11
|
+
int32LE :unknown1, {:value => 0}
|
12
|
+
string :target_info, {:value => "", :size => 0}
|
13
|
+
int32LE :unknown2, {:value => 0}
|
14
|
+
|
15
|
+
def parse(str, offset=0)
|
16
|
+
# 28 is the length of all fields before the variable-length
|
17
|
+
# target_info field.
|
18
|
+
if str.size > 28
|
19
|
+
enable(:target_info)
|
20
|
+
# Grab everything except the last 4 bytes (which will be :unknown2)
|
21
|
+
self[:target_info].value = str[28..-5]
|
22
|
+
end
|
23
|
+
super
|
24
|
+
end
|
14
25
|
end
|
15
26
|
|
16
27
|
end
|
17
|
-
end
|
28
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Net
|
2
|
+
module NTLM
|
3
|
+
class Client
|
4
|
+
|
5
|
+
DEFAULT_FLAGS = NTLM::FLAGS[:UNICODE] | NTLM::FLAGS[:OEM] |
|
6
|
+
NTLM::FLAGS[:SIGN] | NTLM::FLAGS[:SEAL] | NTLM::FLAGS[:REQUEST_TARGET] |
|
7
|
+
NTLM::FLAGS[:NTLM] | NTLM::FLAGS[:ALWAYS_SIGN] | NTLM::FLAGS[:NTLM2_KEY] |
|
8
|
+
NTLM::FLAGS[:KEY128] | NTLM::FLAGS[:KEY_EXCHANGE] | NTLM::FLAGS[:KEY56]
|
9
|
+
|
10
|
+
attr_reader :username, :password, :domain, :workstation, :flags
|
11
|
+
|
12
|
+
# @note All string parameters should be encoded in UTF-8. The proper
|
13
|
+
# final encoding for placing in the various {Message messages} will be
|
14
|
+
# chosen based on negotiation with the server.
|
15
|
+
#
|
16
|
+
# @param username [String]
|
17
|
+
# @param password [String]
|
18
|
+
# @option opts [String] :domain where we're authenticating to
|
19
|
+
# @option opts [String] :workstation local workstation name
|
20
|
+
# @option opts [Fixnum] :flags (DEFAULT_FLAGS) see Net::NTLM::Message::Type1.flag
|
21
|
+
def initialize(username, password, opts = {})
|
22
|
+
@username = username
|
23
|
+
@password = password
|
24
|
+
@domain = opts[:domain] || nil
|
25
|
+
@workstation = opts[:workstation] || nil
|
26
|
+
@flags = opts[:flags] || DEFAULT_FLAGS
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [NTLM::Message]
|
30
|
+
def init_context(resp = nil)
|
31
|
+
if resp.nil?
|
32
|
+
@session = nil
|
33
|
+
type1_message
|
34
|
+
else
|
35
|
+
@session = Client::Session.new(self, Net::NTLM::Message.decode64(resp))
|
36
|
+
@session.authenticate!
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Client::Session]
|
41
|
+
def session
|
42
|
+
@session
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @return [Message::Type1]
|
48
|
+
def type1_message
|
49
|
+
type1 = Message::Type1.new
|
50
|
+
type1[:flag].value = flags
|
51
|
+
type1.domain = domain if domain
|
52
|
+
type1.workstation = workstation if workstation
|
53
|
+
|
54
|
+
type1
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
require "net/ntlm/client/session"
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Net
|
2
|
+
module NTLM
|
3
|
+
class Client::Session
|
4
|
+
|
5
|
+
VERSION_MAGIC = "\x01\x00\x00\x00"
|
6
|
+
TIME_OFFSET = 11644473600
|
7
|
+
MAX64 = 0xffffffffffffffff
|
8
|
+
CLIENT_TO_SERVER_SIGNING = "session key to client-to-server signing key magic constant\0"
|
9
|
+
SERVER_TO_CLIENT_SIGNING = "session key to server-to-client signing key magic constant\0"
|
10
|
+
CLIENT_TO_SERVER_SEALING = "session key to client-to-server sealing key magic constant\0"
|
11
|
+
SERVER_TO_CLIENT_SEALING = "session key to server-to-client sealing key magic constant\0"
|
12
|
+
|
13
|
+
attr_reader :client, :challenge_message
|
14
|
+
|
15
|
+
# @param client [Net::NTLM::Client] the client instance
|
16
|
+
# @param challenge_message [Net::NTLM::Message::Type2] server message
|
17
|
+
def initialize(client, challenge_message)
|
18
|
+
@client = client
|
19
|
+
@challenge_message = challenge_message
|
20
|
+
end
|
21
|
+
|
22
|
+
# Generate an NTLMv2 AUTHENTICATE_MESSAGE
|
23
|
+
# @see http://msdn.microsoft.com/en-us/library/cc236643.aspx
|
24
|
+
# @return [Net::NTLM::Message::Type3]
|
25
|
+
def authenticate!
|
26
|
+
calculate_user_session_key!
|
27
|
+
type3_opts = {
|
28
|
+
:lm_response => lmv2_resp,
|
29
|
+
:ntlm_response => ntlmv2_resp,
|
30
|
+
:domain => domain,
|
31
|
+
:user => username,
|
32
|
+
:workstation => workstation,
|
33
|
+
:flag => (challenge_message.flag & client.flags)
|
34
|
+
}
|
35
|
+
t3 = Message::Type3.create type3_opts
|
36
|
+
if negotiate_key_exchange?
|
37
|
+
t3.enable(:session_key)
|
38
|
+
rc4 = OpenSSL::Cipher::Cipher.new("rc4")
|
39
|
+
rc4.encrypt
|
40
|
+
rc4.key = user_session_key
|
41
|
+
sk = rc4.update master_key
|
42
|
+
sk << rc4.final
|
43
|
+
t3.session_key = sk
|
44
|
+
end
|
45
|
+
t3
|
46
|
+
end
|
47
|
+
|
48
|
+
def sign_message(message)
|
49
|
+
seq = sequence
|
50
|
+
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, client_sign_key, "#{seq}#{message}")[0..7]
|
51
|
+
if negotiate_key_exchange?
|
52
|
+
sig = client_cipher.update sig
|
53
|
+
sig << client_cipher.final
|
54
|
+
end
|
55
|
+
"#{VERSION_MAGIC}#{sig}#{seq}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def verify_signature(signature, message)
|
59
|
+
seq = signature[-4..-1]
|
60
|
+
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, server_sign_key, "#{seq}#{message}")[0..7]
|
61
|
+
if negotiate_key_exchange?
|
62
|
+
sig = server_cipher.update sig
|
63
|
+
sig << server_cipher.final
|
64
|
+
end
|
65
|
+
"#{VERSION_MAGIC}#{sig}#{seq}" == signature
|
66
|
+
end
|
67
|
+
|
68
|
+
def seal_message(message)
|
69
|
+
emessage = client_cipher.update(message)
|
70
|
+
emessage + client_cipher.final
|
71
|
+
end
|
72
|
+
|
73
|
+
def unseal_message(emessage)
|
74
|
+
message = server_cipher.update(emessage)
|
75
|
+
message + server_cipher.final
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
|
82
|
+
def user_session_key
|
83
|
+
@user_session_key ||= nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def master_key
|
87
|
+
@master_key ||= begin
|
88
|
+
if negotiate_key_exchange?
|
89
|
+
OpenSSL::Cipher.new("rc4").random_key
|
90
|
+
else
|
91
|
+
user_session_key
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def sequence
|
97
|
+
[raw_sequence].pack("V*")
|
98
|
+
end
|
99
|
+
|
100
|
+
def raw_sequence
|
101
|
+
if defined? @raw_sequence
|
102
|
+
@raw_sequence += 1
|
103
|
+
else
|
104
|
+
@raw_sequence = 0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def client_sign_key
|
109
|
+
@client_sign_key ||= OpenSSL::Digest::MD5.digest "#{master_key}#{CLIENT_TO_SERVER_SIGNING}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def server_sign_key
|
113
|
+
@server_sign_key ||= OpenSSL::Digest::MD5.digest "#{master_key}#{SERVER_TO_CLIENT_SIGNING}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def client_seal_key
|
117
|
+
@client_seal_key ||= OpenSSL::Digest::MD5.digest "#{master_key}#{CLIENT_TO_SERVER_SEALING}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def server_seal_key
|
121
|
+
@server_seal_key ||= OpenSSL::Digest::MD5.digest "#{master_key}#{SERVER_TO_CLIENT_SEALING}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def client_cipher
|
125
|
+
@client_cipher ||=
|
126
|
+
begin
|
127
|
+
rc4 = OpenSSL::Cipher::Cipher.new("rc4")
|
128
|
+
rc4.encrypt
|
129
|
+
rc4.key = client_seal_key
|
130
|
+
rc4
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def server_cipher
|
135
|
+
@server_cipher ||=
|
136
|
+
begin
|
137
|
+
rc4 = OpenSSL::Cipher::Cipher.new("rc4")
|
138
|
+
rc4.decrypt
|
139
|
+
rc4.key = server_seal_key
|
140
|
+
rc4
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def client_challenge
|
145
|
+
@client_challenge ||= NTLM.pack_int64le(rand(MAX64))
|
146
|
+
end
|
147
|
+
|
148
|
+
def server_challenge
|
149
|
+
@server_challenge ||= challenge_message[:challenge].serialize
|
150
|
+
end
|
151
|
+
|
152
|
+
# epoch -> milsec from Jan 1, 1601
|
153
|
+
# @see http://support.microsoft.com/kb/188768
|
154
|
+
def timestamp
|
155
|
+
@timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
|
156
|
+
end
|
157
|
+
|
158
|
+
def use_oem_strings?
|
159
|
+
challenge_message.has_flag? :OEM
|
160
|
+
end
|
161
|
+
|
162
|
+
def negotiate_key_exchange?
|
163
|
+
challenge_message.has_flag? :KEY_EXCHANGE
|
164
|
+
end
|
165
|
+
|
166
|
+
def username
|
167
|
+
oem_or_unicode_str client.username
|
168
|
+
end
|
169
|
+
|
170
|
+
def password
|
171
|
+
oem_or_unicode_str client.password
|
172
|
+
end
|
173
|
+
|
174
|
+
def workstation
|
175
|
+
(client.domain ? oem_or_unicode_str(client.workstation) : "")
|
176
|
+
end
|
177
|
+
|
178
|
+
def domain
|
179
|
+
(client.domain ? oem_or_unicode_str(client.domain) : "")
|
180
|
+
end
|
181
|
+
|
182
|
+
def oem_or_unicode_str(str)
|
183
|
+
if use_oem_strings?
|
184
|
+
NTLM::EncodeUtil.decode_utf16le str
|
185
|
+
else
|
186
|
+
NTLM::EncodeUtil.encode_utf16le str
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def ntlmv2_hash
|
191
|
+
@ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
|
192
|
+
end
|
193
|
+
|
194
|
+
def calculate_user_session_key!
|
195
|
+
@user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
|
196
|
+
end
|
197
|
+
|
198
|
+
def lmv2_resp
|
199
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + client_challenge) + client_challenge
|
200
|
+
end
|
201
|
+
|
202
|
+
def ntlmv2_resp
|
203
|
+
nt_proof_str + blob
|
204
|
+
end
|
205
|
+
|
206
|
+
def nt_proof_str
|
207
|
+
@nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + blob)
|
208
|
+
end
|
209
|
+
|
210
|
+
def blob
|
211
|
+
@blob ||=
|
212
|
+
begin
|
213
|
+
b = Blob.new
|
214
|
+
b.timestamp = timestamp
|
215
|
+
b.challenge = client_challenge
|
216
|
+
b.target_info = challenge_message.target_info
|
217
|
+
b.serialize
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|