File: //usr/share/rubygems-integration/all/gems/rubyntlm-0.6.3/lib/net/ntlm/client/session.rb
module Net
module NTLM
class Client::Session
VERSION_MAGIC = "\x01\x00\x00\x00"
TIME_OFFSET = 11644473600
MAX64 = 0xffffffffffffffff
CLIENT_TO_SERVER_SIGNING = "session key to client-to-server signing key magic constant\0"
SERVER_TO_CLIENT_SIGNING = "session key to server-to-client signing key magic constant\0"
CLIENT_TO_SERVER_SEALING = "session key to client-to-server sealing key magic constant\0"
SERVER_TO_CLIENT_SEALING = "session key to server-to-client sealing key magic constant\0"
attr_reader :client, :challenge_message, :channel_binding
# @param client [Net::NTLM::Client] the client instance
# @param challenge_message [Net::NTLM::Message::Type2] server message
def initialize(client, challenge_message, channel_binding = nil)
@client = client
@challenge_message = challenge_message
@channel_binding = channel_binding
end
# Generate an NTLMv2 AUTHENTICATE_MESSAGE
# @see http://msdn.microsoft.com/en-us/library/cc236643.aspx
# @return [Net::NTLM::Message::Type3]
def authenticate!
calculate_user_session_key!
type3_opts = {
:lm_response => lmv2_resp,
:ntlm_response => ntlmv2_resp,
:domain => domain,
:user => username,
:workstation => workstation,
:flag => (challenge_message.flag & client.flags)
}
t3 = Message::Type3.create type3_opts
if negotiate_key_exchange?
t3.enable(:session_key)
rc4 = OpenSSL::Cipher.new("rc4")
rc4.encrypt
rc4.key = user_session_key
sk = rc4.update exported_session_key
sk << rc4.final
t3.session_key = sk
end
t3
end
def exported_session_key
@exported_session_key ||=
begin
if negotiate_key_exchange?
OpenSSL::Cipher.new("rc4").random_key
else
user_session_key
end
end
end
def sign_message(message)
seq = sequence
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, client_sign_key, "#{seq}#{message}")[0..7]
if negotiate_key_exchange?
sig = client_cipher.update sig
sig << client_cipher.final
end
"#{VERSION_MAGIC}#{sig}#{seq}"
end
def verify_signature(signature, message)
seq = signature[-4..-1]
sig = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, server_sign_key, "#{seq}#{message}")[0..7]
if negotiate_key_exchange?
sig = server_cipher.update sig
sig << server_cipher.final
end
"#{VERSION_MAGIC}#{sig}#{seq}" == signature
end
def seal_message(message)
emessage = client_cipher.update(message)
emessage + client_cipher.final
end
def unseal_message(emessage)
message = server_cipher.update(emessage)
message + server_cipher.final
end
private
def user_session_key
@user_session_key ||= nil
end
def sequence
[raw_sequence].pack("V*")
end
def raw_sequence
if defined? @raw_sequence
@raw_sequence += 1
else
@raw_sequence = 0
end
end
def client_sign_key
@client_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SIGNING}"
end
def server_sign_key
@server_sign_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SIGNING}"
end
def client_seal_key
@client_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{CLIENT_TO_SERVER_SEALING}"
end
def server_seal_key
@server_seal_key ||= OpenSSL::Digest::MD5.digest "#{exported_session_key}#{SERVER_TO_CLIENT_SEALING}"
end
def client_cipher
@client_cipher ||=
begin
rc4 = OpenSSL::Cipher.new("rc4")
rc4.encrypt
rc4.key = client_seal_key
rc4
end
end
def server_cipher
@server_cipher ||=
begin
rc4 = OpenSSL::Cipher.new("rc4")
rc4.decrypt
rc4.key = server_seal_key
rc4
end
end
def client_challenge
@client_challenge ||= NTLM.pack_int64le(rand(MAX64))
end
def server_challenge
@server_challenge ||= challenge_message[:challenge].serialize
end
# epoch -> milsec from Jan 1, 1601
# @see http://support.microsoft.com/kb/188768
def timestamp
@timestamp ||= 10_000_000 * (Time.now.to_i + TIME_OFFSET)
end
def use_oem_strings?
challenge_message.has_flag? :OEM
end
def negotiate_key_exchange?
challenge_message.has_flag? :KEY_EXCHANGE
end
def username
oem_or_unicode_str client.username
end
def password
oem_or_unicode_str client.password
end
def workstation
(client.workstation ? oem_or_unicode_str(client.workstation) : "")
end
def domain
(client.domain ? oem_or_unicode_str(client.domain) : "")
end
def oem_or_unicode_str(str)
if use_oem_strings?
NTLM::EncodeUtil.decode_utf16le str
else
NTLM::EncodeUtil.encode_utf16le str
end
end
def ntlmv2_hash
@ntlmv2_hash ||= NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
end
def calculate_user_session_key!
@user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
end
def lmv2_resp
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + client_challenge) + client_challenge
end
def ntlmv2_resp
nt_proof_str + blob
end
def nt_proof_str
@nt_proof_str ||= OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, server_challenge + blob)
end
def blob
@blob ||=
begin
b = Blob.new
b.timestamp = timestamp
b.challenge = client_challenge
b.target_info = target_info
b.serialize
end
end
def target_info
@target_info ||= begin
if channel_binding
t = Net::NTLM::TargetInfo.new(challenge_message.target_info)
av_id = Net::NTLM::TargetInfo::MSV_AV_CHANNEL_BINDINGS
t.av_pairs[av_id] = channel_binding.channel_binding_token
t.to_s
else
challenge_message.target_info
end
end
end
end
end
end