rubyntlm 0.6.1 → 0.6.2
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/.gitignore +4 -3
- data/.rspec +2 -2
- data/.travis.yml +13 -12
- data/CHANGELOG.md +118 -6
- data/Gemfile +3 -3
- data/LICENSE +19 -19
- data/Rakefile +25 -22
- data/lib/net/ntlm.rb +266 -266
- data/lib/net/ntlm/blob.rb +28 -28
- data/lib/net/ntlm/channel_binding.rb +65 -65
- data/lib/net/ntlm/client.rb +65 -65
- data/lib/net/ntlm/client/session.rb +237 -237
- data/lib/net/ntlm/encode_util.rb +48 -48
- data/lib/net/ntlm/exceptions.rb +14 -14
- data/lib/net/ntlm/field.rb +34 -34
- data/lib/net/ntlm/field_set.rb +129 -129
- data/lib/net/ntlm/int16_le.rb +25 -25
- data/lib/net/ntlm/int32_le.rb +24 -24
- data/lib/net/ntlm/int64_le.rb +25 -25
- data/lib/net/ntlm/message.rb +129 -129
- data/lib/net/ntlm/message/type0.rb +16 -16
- data/lib/net/ntlm/message/type1.rb +18 -18
- data/lib/net/ntlm/message/type2.rb +102 -102
- data/lib/net/ntlm/message/type3.rb +131 -131
- data/lib/net/ntlm/security_buffer.rb +47 -47
- data/lib/net/ntlm/string.rb +34 -34
- data/lib/net/ntlm/target_info.rb +89 -89
- data/lib/net/ntlm/version.rb +11 -11
- data/rubyntlm.gemspec +29 -28
- data/spec/lib/net/ntlm/blob_spec.rb +16 -16
- data/spec/lib/net/ntlm/channel_binding_spec.rb +17 -17
- data/spec/lib/net/ntlm/client/session_spec.rb +68 -68
- data/spec/lib/net/ntlm/client_spec.rb +64 -64
- data/spec/lib/net/ntlm/encode_util_spec.rb +16 -16
- data/spec/lib/net/ntlm/field_set_spec.rb +33 -33
- data/spec/lib/net/ntlm/field_spec.rb +34 -34
- data/spec/lib/net/ntlm/int16_le_spec.rb +17 -17
- data/spec/lib/net/ntlm/int32_le_spec.rb +18 -18
- data/spec/lib/net/ntlm/int64_le_spec.rb +18 -18
- data/spec/lib/net/ntlm/message/type0_spec.rb +20 -20
- data/spec/lib/net/ntlm/message/type1_spec.rb +131 -131
- data/spec/lib/net/ntlm/message/type2_spec.rb +132 -132
- data/spec/lib/net/ntlm/message/type3_spec.rb +225 -225
- data/spec/lib/net/ntlm/message_spec.rb +16 -16
- data/spec/lib/net/ntlm/security_buffer_spec.rb +64 -64
- data/spec/lib/net/ntlm/string_spec.rb +72 -72
- data/spec/lib/net/ntlm/target_info_spec.rb +76 -76
- data/spec/lib/net/ntlm/version_spec.rb +27 -27
- data/spec/lib/net/ntlm_spec.rb +127 -127
- data/spec/spec_helper.rb +22 -22
- data/spec/support/certificates/sha_256_hash.pem +19 -19
- data/spec/support/shared/examples/net/ntlm/field_shared.rb +25 -25
- data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +239 -239
- data/spec/support/shared/examples/net/ntlm/int_shared.rb +43 -43
- data/spec/support/shared/examples/net/ntlm/message_shared.rb +35 -35
- metadata +17 -3
data/lib/net/ntlm/blob.rb
CHANGED
@@ -1,28 +1,28 @@
|
|
1
|
-
module Net
|
2
|
-
module NTLM
|
3
|
-
|
4
|
-
BLOB_SIGN = 0x00000101
|
5
|
-
|
6
|
-
class Blob < FieldSet
|
7
|
-
int32LE :blob_signature, {:value => BLOB_SIGN}
|
8
|
-
int32LE :reserved, {:value => 0}
|
9
|
-
int64LE :timestamp, {:value => 0}
|
10
|
-
string :challenge, {:value => "", :size => 8}
|
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
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
1
|
+
module Net
|
2
|
+
module NTLM
|
3
|
+
|
4
|
+
BLOB_SIGN = 0x00000101
|
5
|
+
|
6
|
+
class Blob < FieldSet
|
7
|
+
int32LE :blob_signature, {:value => BLOB_SIGN}
|
8
|
+
int32LE :reserved, {:value => 0}
|
9
|
+
int64LE :timestamp, {:value => 0}
|
10
|
+
string :challenge, {:value => "", :size => 8}
|
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
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -1,65 +1,65 @@
|
|
1
|
-
module Net
|
2
|
-
module NTLM
|
3
|
-
class ChannelBinding
|
4
|
-
|
5
|
-
# Creates a ChannelBinding used for Extended Protection Authentication
|
6
|
-
# @see http://blogs.msdn.com/b/openspecification/archive/2013/03/26/ntlm-and-channel-binding-hash-aka-exteneded-protection-for-authentication.aspx
|
7
|
-
#
|
8
|
-
# @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing
|
9
|
-
# the outer TLS channel
|
10
|
-
# @return [NTLM::ChannelBinding] A ChannelBinding holding a token that can be
|
11
|
-
# embedded in a {Type3} message
|
12
|
-
def self.create(outer_channel)
|
13
|
-
new(outer_channel)
|
14
|
-
end
|
15
|
-
|
16
|
-
# @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing
|
17
|
-
# the outer TLS channel
|
18
|
-
def initialize(outer_channel)
|
19
|
-
@channel = outer_channel
|
20
|
-
@unique_prefix = 'tls-server-end-point'
|
21
|
-
@initiator_addtype = 0
|
22
|
-
@initiator_address_length = 0
|
23
|
-
@acceptor_addrtype = 0
|
24
|
-
@acceptor_address_length = 0
|
25
|
-
end
|
26
|
-
|
27
|
-
attr_reader :channel, :unique_prefix, :initiator_addtype
|
28
|
-
attr_reader :initiator_address_length, :acceptor_addrtype
|
29
|
-
attr_reader :acceptor_address_length
|
30
|
-
|
31
|
-
# Returns a channel binding hash acceptable for use as a AV_PAIR MsvAvChannelBindings
|
32
|
-
# field value as specified in the NTLM protocol
|
33
|
-
#
|
34
|
-
# @return [String] MD5 hash of gss_channel_bindings_struct
|
35
|
-
def channel_binding_token
|
36
|
-
@channel_binding_token ||= OpenSSL::Digest::MD5.new(gss_channel_bindings_struct).digest
|
37
|
-
end
|
38
|
-
|
39
|
-
def gss_channel_bindings_struct
|
40
|
-
@gss_channel_bindings_struct ||= begin
|
41
|
-
token = [initiator_addtype].pack('I')
|
42
|
-
token << [initiator_address_length].pack('I')
|
43
|
-
token << [acceptor_addrtype].pack('I')
|
44
|
-
token << [acceptor_address_length].pack('I')
|
45
|
-
token << [application_data.length].pack('I')
|
46
|
-
token << application_data
|
47
|
-
token
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def channel_hash
|
52
|
-
@channel_hash ||= OpenSSL::Digest::SHA256.new(channel.to_der)
|
53
|
-
end
|
54
|
-
|
55
|
-
def application_data
|
56
|
-
@application_data ||= begin
|
57
|
-
data = unique_prefix
|
58
|
-
data << ':'
|
59
|
-
data << channel_hash.digest
|
60
|
-
data
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
1
|
+
module Net
|
2
|
+
module NTLM
|
3
|
+
class ChannelBinding
|
4
|
+
|
5
|
+
# Creates a ChannelBinding used for Extended Protection Authentication
|
6
|
+
# @see http://blogs.msdn.com/b/openspecification/archive/2013/03/26/ntlm-and-channel-binding-hash-aka-exteneded-protection-for-authentication.aspx
|
7
|
+
#
|
8
|
+
# @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing
|
9
|
+
# the outer TLS channel
|
10
|
+
# @return [NTLM::ChannelBinding] A ChannelBinding holding a token that can be
|
11
|
+
# embedded in a {Type3} message
|
12
|
+
def self.create(outer_channel)
|
13
|
+
new(outer_channel)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing
|
17
|
+
# the outer TLS channel
|
18
|
+
def initialize(outer_channel)
|
19
|
+
@channel = outer_channel
|
20
|
+
@unique_prefix = 'tls-server-end-point'
|
21
|
+
@initiator_addtype = 0
|
22
|
+
@initiator_address_length = 0
|
23
|
+
@acceptor_addrtype = 0
|
24
|
+
@acceptor_address_length = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :channel, :unique_prefix, :initiator_addtype
|
28
|
+
attr_reader :initiator_address_length, :acceptor_addrtype
|
29
|
+
attr_reader :acceptor_address_length
|
30
|
+
|
31
|
+
# Returns a channel binding hash acceptable for use as a AV_PAIR MsvAvChannelBindings
|
32
|
+
# field value as specified in the NTLM protocol
|
33
|
+
#
|
34
|
+
# @return [String] MD5 hash of gss_channel_bindings_struct
|
35
|
+
def channel_binding_token
|
36
|
+
@channel_binding_token ||= OpenSSL::Digest::MD5.new(gss_channel_bindings_struct).digest
|
37
|
+
end
|
38
|
+
|
39
|
+
def gss_channel_bindings_struct
|
40
|
+
@gss_channel_bindings_struct ||= begin
|
41
|
+
token = [initiator_addtype].pack('I')
|
42
|
+
token << [initiator_address_length].pack('I')
|
43
|
+
token << [acceptor_addrtype].pack('I')
|
44
|
+
token << [acceptor_address_length].pack('I')
|
45
|
+
token << [application_data.length].pack('I')
|
46
|
+
token << application_data
|
47
|
+
token
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def channel_hash
|
52
|
+
@channel_hash ||= OpenSSL::Digest::SHA256.new(channel.to_der)
|
53
|
+
end
|
54
|
+
|
55
|
+
def application_data
|
56
|
+
@application_data ||= begin
|
57
|
+
data = unique_prefix
|
58
|
+
data << ':'
|
59
|
+
data << channel_hash.digest
|
60
|
+
data
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/net/ntlm/client.rb
CHANGED
@@ -1,65 +1,65 @@
|
|
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, channel_binding = nil)
|
31
|
-
if resp.nil?
|
32
|
-
@session = nil
|
33
|
-
type1_message
|
34
|
-
else
|
35
|
-
@session = Client::Session.new(self, Net::NTLM::Message.decode64(resp), channel_binding)
|
36
|
-
@session.authenticate!
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# @return [Client::Session]
|
41
|
-
def session
|
42
|
-
@session
|
43
|
-
end
|
44
|
-
|
45
|
-
def session_key
|
46
|
-
@session.exported_session_key
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
# @return [Message::Type1]
|
52
|
-
def type1_message
|
53
|
-
type1 = Message::Type1.new
|
54
|
-
type1[:flag].value = flags
|
55
|
-
type1.domain = domain if domain
|
56
|
-
type1.workstation = workstation if workstation
|
57
|
-
|
58
|
-
type1
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
require "net/ntlm/client/session"
|
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, channel_binding = nil)
|
31
|
+
if resp.nil?
|
32
|
+
@session = nil
|
33
|
+
type1_message
|
34
|
+
else
|
35
|
+
@session = Client::Session.new(self, Net::NTLM::Message.decode64(resp), channel_binding)
|
36
|
+
@session.authenticate!
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Client::Session]
|
41
|
+
def session
|
42
|
+
@session
|
43
|
+
end
|
44
|
+
|
45
|
+
def session_key
|
46
|
+
@session.exported_session_key
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# @return [Message::Type1]
|
52
|
+
def type1_message
|
53
|
+
type1 = Message::Type1.new
|
54
|
+
type1[:flag].value = flags
|
55
|
+
type1.domain = domain if domain
|
56
|
+
type1.workstation = workstation if workstation
|
57
|
+
|
58
|
+
type1
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
require "net/ntlm/client/session"
|
@@ -1,237 +1,237 @@
|
|
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, :channel_binding
|
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, channel_binding = nil)
|
18
|
-
@client = client
|
19
|
-
@challenge_message = challenge_message
|
20
|
-
@channel_binding = channel_binding
|
21
|
-
end
|
22
|
-
|
23
|
-
# Generate an NTLMv2 AUTHENTICATE_MESSAGE
|
24
|
-
# @see http://msdn.microsoft.com/en-us/library/cc236643.aspx
|
25
|
-
# @return [Net::NTLM::Message::Type3]
|
26
|
-
def authenticate!
|
27
|
-
calculate_user_session_key!
|
28
|
-
type3_opts = {
|
29
|
-
:lm_response => lmv2_resp,
|
30
|
-
:ntlm_response => ntlmv2_resp,
|
31
|
-
:domain => domain,
|
32
|
-
:user => username,
|
33
|
-
:workstation => workstation,
|
34
|
-
:flag => (challenge_message.flag & client.flags)
|
35
|
-
}
|
36
|
-
t3 = Message::Type3.create type3_opts
|
37
|
-
if negotiate_key_exchange?
|
38
|
-
t3.enable(:session_key)
|
39
|
-
rc4 = OpenSSL::Cipher
|
40
|
-
rc4.encrypt
|
41
|
-
rc4.key = user_session_key
|
42
|
-
sk = rc4.update exported_session_key
|
43
|
-
sk << rc4.final
|
44
|
-
t3.session_key = sk
|
45
|
-
end
|
46
|
-
t3
|
47
|
-
end
|
48
|
-
|
49
|
-
def exported_session_key
|
50
|
-
@exported_session_key ||=
|
51
|
-
begin
|
52
|
-
if negotiate_key_exchange?
|
53
|
-
OpenSSL::Cipher.new("rc4").random_key
|
54
|
-
else
|
55
|
-
user_session_key
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def sign_message(message)
|
61
|
-
seq = sequence
|
62
|
-
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, client_sign_key, "#{seq}#{message}")[0..7]
|
63
|
-
if negotiate_key_exchange?
|
64
|
-
sig = client_cipher.update sig
|
65
|
-
sig << client_cipher.final
|
66
|
-
end
|
67
|
-
"#{VERSION_MAGIC}#{sig}#{seq}"
|
68
|
-
end
|
69
|
-
|
70
|
-
def verify_signature(signature, message)
|
71
|
-
seq = signature[-4..-1]
|
72
|
-
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, server_sign_key, "#{seq}#{message}")[0..7]
|
73
|
-
if negotiate_key_exchange?
|
74
|
-
sig = server_cipher.update sig
|
75
|
-
sig << server_cipher.final
|
76
|
-
end
|
77
|
-
"#{VERSION_MAGIC}#{sig}#{seq}" == signature
|
78
|
-
end
|
79
|
-
|
80
|
-
def seal_message(message)
|
81
|
-
emessage = client_cipher.update(message)
|
82
|
-
emessage + client_cipher.final
|
83
|
-
end
|
84
|
-
|
85
|
-
def unseal_message(emessage)
|
86
|
-
message = server_cipher.update(emessage)
|
87
|
-
message + server_cipher.final
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
|
93
|
-
def user_session_key
|
94
|
-
@user_session_key ||= nil
|
95
|
-
end
|
96
|
-
|
97
|
-
def sequence
|
98
|
-
[raw_sequence].pack("V*")
|
99
|
-
end
|
100
|
-
|
101
|
-
def raw_sequence
|
102
|
-
if defined? @raw_sequence
|
103
|
-
@raw_sequence += 1
|
104
|
-
else
|
105
|
-
@raw_sequence = 0
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def client_sign_key
|
110
|
-
@client_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SIGNING}"
|
111
|
-
end
|
112
|
-
|
113
|
-
def server_sign_key
|
114
|
-
@server_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SIGNING}"
|
115
|
-
end
|
116
|
-
|
117
|
-
def client_seal_key
|
118
|
-
@client_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SEALING}"
|
119
|
-
end
|
120
|
-
|
121
|
-
def server_seal_key
|
122
|
-
@server_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SEALING}"
|
123
|
-
end
|
124
|
-
|
125
|
-
def client_cipher
|
126
|
-
@client_cipher ||=
|
127
|
-
begin
|
128
|
-
rc4 = OpenSSL::Cipher
|
129
|
-
rc4.encrypt
|
130
|
-
rc4.key = client_seal_key
|
131
|
-
rc4
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def server_cipher
|
136
|
-
@server_cipher ||=
|
137
|
-
begin
|
138
|
-
rc4 = OpenSSL::Cipher
|
139
|
-
rc4.decrypt
|
140
|
-
rc4.key = server_seal_key
|
141
|
-
rc4
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def client_challenge
|
146
|
-
@client_challenge ||= NTLM.pack_int64le(rand(MAX64))
|
147
|
-
end
|
148
|
-
|
149
|
-
def server_challenge
|
150
|
-
@server_challenge ||= challenge_message[:challenge].serialize
|
151
|
-
end
|
152
|
-
|
153
|
-
# epoch -> milsec from Jan 1, 1601
|
154
|
-
# @see http://support.microsoft.com/kb/188768
|
155
|
-
def timestamp
|
156
|
-
@timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
|
157
|
-
end
|
158
|
-
|
159
|
-
def use_oem_strings?
|
160
|
-
challenge_message.has_flag? :OEM
|
161
|
-
end
|
162
|
-
|
163
|
-
def negotiate_key_exchange?
|
164
|
-
challenge_message.has_flag? :KEY_EXCHANGE
|
165
|
-
end
|
166
|
-
|
167
|
-
def username
|
168
|
-
oem_or_unicode_str client.username
|
169
|
-
end
|
170
|
-
|
171
|
-
def password
|
172
|
-
oem_or_unicode_str client.password
|
173
|
-
end
|
174
|
-
|
175
|
-
def workstation
|
176
|
-
(client.workstation ? oem_or_unicode_str(client.workstation) : "")
|
177
|
-
end
|
178
|
-
|
179
|
-
def domain
|
180
|
-
(client.domain ? oem_or_unicode_str(client.domain) : "")
|
181
|
-
end
|
182
|
-
|
183
|
-
def oem_or_unicode_str(str)
|
184
|
-
if use_oem_strings?
|
185
|
-
NTLM::EncodeUtil.decode_utf16le str
|
186
|
-
else
|
187
|
-
NTLM::EncodeUtil.encode_utf16le str
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def ntlmv2_hash
|
192
|
-
@ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
|
193
|
-
end
|
194
|
-
|
195
|
-
def calculate_user_session_key!
|
196
|
-
@user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
|
197
|
-
end
|
198
|
-
|
199
|
-
def lmv2_resp
|
200
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + client_challenge) + client_challenge
|
201
|
-
end
|
202
|
-
|
203
|
-
def ntlmv2_resp
|
204
|
-
nt_proof_str + blob
|
205
|
-
end
|
206
|
-
|
207
|
-
def nt_proof_str
|
208
|
-
@nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + blob)
|
209
|
-
end
|
210
|
-
|
211
|
-
def blob
|
212
|
-
@blob ||=
|
213
|
-
begin
|
214
|
-
b = Blob.new
|
215
|
-
b.timestamp = timestamp
|
216
|
-
b.challenge = client_challenge
|
217
|
-
b.target_info = target_info
|
218
|
-
b.serialize
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
def target_info
|
223
|
-
@target_info ||= begin
|
224
|
-
if channel_binding
|
225
|
-
t = Net::NTLM::TargetInfo.new(challenge_message.target_info)
|
226
|
-
av_id = Net::NTLM::TargetInfo::MSV_AV_CHANNEL_BINDINGS
|
227
|
-
t.av_pairs[av_id] = channel_binding.channel_binding_token
|
228
|
-
t.to_s
|
229
|
-
else
|
230
|
-
challenge_message.target_info
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
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, :channel_binding
|
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, channel_binding = nil)
|
18
|
+
@client = client
|
19
|
+
@challenge_message = challenge_message
|
20
|
+
@channel_binding = channel_binding
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generate an NTLMv2 AUTHENTICATE_MESSAGE
|
24
|
+
# @see http://msdn.microsoft.com/en-us/library/cc236643.aspx
|
25
|
+
# @return [Net::NTLM::Message::Type3]
|
26
|
+
def authenticate!
|
27
|
+
calculate_user_session_key!
|
28
|
+
type3_opts = {
|
29
|
+
:lm_response => lmv2_resp,
|
30
|
+
:ntlm_response => ntlmv2_resp,
|
31
|
+
:domain => domain,
|
32
|
+
:user => username,
|
33
|
+
:workstation => workstation,
|
34
|
+
:flag => (challenge_message.flag & client.flags)
|
35
|
+
}
|
36
|
+
t3 = Message::Type3.create type3_opts
|
37
|
+
if negotiate_key_exchange?
|
38
|
+
t3.enable(:session_key)
|
39
|
+
rc4 = OpenSSL::Cipher.new("rc4")
|
40
|
+
rc4.encrypt
|
41
|
+
rc4.key = user_session_key
|
42
|
+
sk = rc4.update exported_session_key
|
43
|
+
sk << rc4.final
|
44
|
+
t3.session_key = sk
|
45
|
+
end
|
46
|
+
t3
|
47
|
+
end
|
48
|
+
|
49
|
+
def exported_session_key
|
50
|
+
@exported_session_key ||=
|
51
|
+
begin
|
52
|
+
if negotiate_key_exchange?
|
53
|
+
OpenSSL::Cipher.new("rc4").random_key
|
54
|
+
else
|
55
|
+
user_session_key
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def sign_message(message)
|
61
|
+
seq = sequence
|
62
|
+
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, client_sign_key, "#{seq}#{message}")[0..7]
|
63
|
+
if negotiate_key_exchange?
|
64
|
+
sig = client_cipher.update sig
|
65
|
+
sig << client_cipher.final
|
66
|
+
end
|
67
|
+
"#{VERSION_MAGIC}#{sig}#{seq}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def verify_signature(signature, message)
|
71
|
+
seq = signature[-4..-1]
|
72
|
+
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, server_sign_key, "#{seq}#{message}")[0..7]
|
73
|
+
if negotiate_key_exchange?
|
74
|
+
sig = server_cipher.update sig
|
75
|
+
sig << server_cipher.final
|
76
|
+
end
|
77
|
+
"#{VERSION_MAGIC}#{sig}#{seq}" == signature
|
78
|
+
end
|
79
|
+
|
80
|
+
def seal_message(message)
|
81
|
+
emessage = client_cipher.update(message)
|
82
|
+
emessage + client_cipher.final
|
83
|
+
end
|
84
|
+
|
85
|
+
def unseal_message(emessage)
|
86
|
+
message = server_cipher.update(emessage)
|
87
|
+
message + server_cipher.final
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
|
93
|
+
def user_session_key
|
94
|
+
@user_session_key ||= nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def sequence
|
98
|
+
[raw_sequence].pack("V*")
|
99
|
+
end
|
100
|
+
|
101
|
+
def raw_sequence
|
102
|
+
if defined? @raw_sequence
|
103
|
+
@raw_sequence += 1
|
104
|
+
else
|
105
|
+
@raw_sequence = 0
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def client_sign_key
|
110
|
+
@client_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SIGNING}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def server_sign_key
|
114
|
+
@server_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SIGNING}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def client_seal_key
|
118
|
+
@client_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SEALING}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def server_seal_key
|
122
|
+
@server_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SEALING}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def client_cipher
|
126
|
+
@client_cipher ||=
|
127
|
+
begin
|
128
|
+
rc4 = OpenSSL::Cipher.new("rc4")
|
129
|
+
rc4.encrypt
|
130
|
+
rc4.key = client_seal_key
|
131
|
+
rc4
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def server_cipher
|
136
|
+
@server_cipher ||=
|
137
|
+
begin
|
138
|
+
rc4 = OpenSSL::Cipher.new("rc4")
|
139
|
+
rc4.decrypt
|
140
|
+
rc4.key = server_seal_key
|
141
|
+
rc4
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def client_challenge
|
146
|
+
@client_challenge ||= NTLM.pack_int64le(rand(MAX64))
|
147
|
+
end
|
148
|
+
|
149
|
+
def server_challenge
|
150
|
+
@server_challenge ||= challenge_message[:challenge].serialize
|
151
|
+
end
|
152
|
+
|
153
|
+
# epoch -> milsec from Jan 1, 1601
|
154
|
+
# @see http://support.microsoft.com/kb/188768
|
155
|
+
def timestamp
|
156
|
+
@timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
|
157
|
+
end
|
158
|
+
|
159
|
+
def use_oem_strings?
|
160
|
+
challenge_message.has_flag? :OEM
|
161
|
+
end
|
162
|
+
|
163
|
+
def negotiate_key_exchange?
|
164
|
+
challenge_message.has_flag? :KEY_EXCHANGE
|
165
|
+
end
|
166
|
+
|
167
|
+
def username
|
168
|
+
oem_or_unicode_str client.username
|
169
|
+
end
|
170
|
+
|
171
|
+
def password
|
172
|
+
oem_or_unicode_str client.password
|
173
|
+
end
|
174
|
+
|
175
|
+
def workstation
|
176
|
+
(client.workstation ? oem_or_unicode_str(client.workstation) : "")
|
177
|
+
end
|
178
|
+
|
179
|
+
def domain
|
180
|
+
(client.domain ? oem_or_unicode_str(client.domain) : "")
|
181
|
+
end
|
182
|
+
|
183
|
+
def oem_or_unicode_str(str)
|
184
|
+
if use_oem_strings?
|
185
|
+
NTLM::EncodeUtil.decode_utf16le str
|
186
|
+
else
|
187
|
+
NTLM::EncodeUtil.encode_utf16le str
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def ntlmv2_hash
|
192
|
+
@ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
|
193
|
+
end
|
194
|
+
|
195
|
+
def calculate_user_session_key!
|
196
|
+
@user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
|
197
|
+
end
|
198
|
+
|
199
|
+
def lmv2_resp
|
200
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + client_challenge) + client_challenge
|
201
|
+
end
|
202
|
+
|
203
|
+
def ntlmv2_resp
|
204
|
+
nt_proof_str + blob
|
205
|
+
end
|
206
|
+
|
207
|
+
def nt_proof_str
|
208
|
+
@nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + blob)
|
209
|
+
end
|
210
|
+
|
211
|
+
def blob
|
212
|
+
@blob ||=
|
213
|
+
begin
|
214
|
+
b = Blob.new
|
215
|
+
b.timestamp = timestamp
|
216
|
+
b.challenge = client_challenge
|
217
|
+
b.target_info = target_info
|
218
|
+
b.serialize
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def target_info
|
223
|
+
@target_info ||= begin
|
224
|
+
if channel_binding
|
225
|
+
t = Net::NTLM::TargetInfo.new(challenge_message.target_info)
|
226
|
+
av_id = Net::NTLM::TargetInfo::MSV_AV_CHANNEL_BINDINGS
|
227
|
+
t.av_pairs[av_id] = channel_binding.channel_binding_token
|
228
|
+
t.to_s
|
229
|
+
else
|
230
|
+
challenge_message.target_info
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|