rubyntlm 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|