rubyntlm-0.6.1/0000755000004100000410000000000012775746221013437 5ustar www-datawww-datarubyntlm-0.6.1/Rakefile0000644000004100000410000000057112775746221015107 0ustar www-datawww-datarequire "bundler/gem_tasks" require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) task :default => :spec desc "Generate code coverage" task :coverage do ENV['COVERAGE'] = 'true' Rake::Task["spec"].execute end desc "Open a Pry console for this library" task :console do require 'pry' require 'net/ntlm' ARGV.clear Pry.start end rubyntlm-0.6.1/Gemfile0000644000004100000410000000005212775746221014727 0ustar www-datawww-datasource 'https://rubygems.org' gemspec rubyntlm-0.6.1/examples/0000755000004100000410000000000012775746221015255 5ustar www-datawww-datarubyntlm-0.6.1/examples/imap.rb0000644000004100000410000000350512775746221016533 0ustar www-datawww-data# $Id: imap.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $ require "net/imap" $:.unshift(File.dirname(__FILE__) + '/../lib') require "net/ntlm" Net::IMAP::debug = true $host = "localhost" $port = 143 $ssl = false $user = nil $passwd = nil module Net class IMAP class NtlmAuthenticator def process(data) case @state when 1 @state = 2 t1 = Net::NTLM::Message::Type1.new() return t1.serialize when 2 @state = 3 t2 = Net::NTLM::Message.parse(data) t3 = t2.response({:user => @user, :password => @password}, {:ntlmv2 => (@ntlm_type == "ntlmv2")}) return t3.serialize end end private def initialize(user, password, ntlm_type = "ntlmv2") @user = user @password = password @ntlm_type = @ntlm_type @state = 1 end end add_authenticator "NTLM", NtlmAuthenticator class ResponseParser def continue_req match(T_PLUS) if lookahead.symbol == T_CRLF # means empty message return ContinuationRequest.new(ResponseText.new(nil, ""), @str) end match(T_SPACE) return ContinuationRequest.new(resp_text, @str) end end end end unless $user and $passwd print "User name: " ($user = $stdin.readline).chomp! print "Password: " ($passwd = $stdin.readline).chomp! end imap = Net::IMAP.new($host, $port, $ssl) imap.authenticate("NTLM", $user, $passwd) imap.examine("Inbox") # imap.search(["RECENT"]).each do |message_id| # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"] # from = envelope.from.nil? ? "" : envelope.from[0].name # subject = envelope.subject # puts "#{message_id} #{from}: \t#{subject}" # end imap.logout # imap.disconnect rubyntlm-0.6.1/examples/smtp.rb0000644000004100000410000000400412775746221016563 0ustar www-datawww-data# $Id: smtp.rb,v 1.2 2006/10/05 01:36:52 koheik Exp $ require 'socket' $:.unshift(File.dirname(__FILE__) + '/../lib') require 'net/ntlm' $user = nil $passwd = nil $host = "localhost" $port = 25 $debug = true def readline(f) (l = f.gets).chomp! puts "srv> " + l if $debug l end def writeline(f, str) puts "cli> " + str if $debug f.print str + "\r\n" end def main s = TCPSocket.new($host, $port) # greetings readline s writeline s, "EHLO #{$host}" while(line = readline(s)) login = true if /^250-AUTH=LOGIN/ =~ line ntlm = true if /^250-AUTH.+NTLM.*/ =~ line break if /^250 OK/ =~ line end unless ntlm and login raise RuntimeError, "it looks like the server doesn't support NTLM Login" end # send Type1 Message t1 = Net::NTLM::Message::Type1.new() writeline s, "AUTH NTLM " + t1.encode64 # receive Type2 Message, i hope line = readline s unless /334 (.+)/ =~ line raise RuntimeError, "i don't recognize this: #{line}" end t2 = Net::NTLM::Message.decode64($1) unless $user and $passwd target = t2.target_name target = Net::NTLM::decode_utf16le(target) if t2.has_flag?(:UNICODE) puts "Target: #{target}" print "User name: " ($user = $stdin.readline).chomp! print "Password: " ($passwd = $stdin.readline).chomp! end # send Type3 Message t3 = t2.response({:user => $user, :password => $passwd}, {:ntlmv2 => true}) writeline s, t3.encode64 # and result is... line = readline s unless /^235(.+)Authentication successful./i =~ line raise RuntimeError, "sorry, authentication failed." end # do real job here like... # from = $user # to = "billg" # writeline s, "MAIL FROM: #{from}" # readline s # writeline s, "RCPT TO: #{to}" # readline s # writeline s, "DATA" # readline s # writeline s, "From: #{from}" # writeline s, "To: #{to}" # writeline s, "blab blab blab..." # writeline s, "#{from}" # writeline s, "." # readline s # say bye writeline s, "QUIT" s.close end main rubyntlm-0.6.1/examples/http.rb0000644000004100000410000000333412775746221016564 0ustar www-datawww-data# $Id: http.rb,v 1.2 2006/10/05 01:36:52 koheik Exp $ require 'socket' $:.unshift(File.dirname(__FILE__) + '/../lib') require 'net/ntlm' $user = nil $passwd = nil $host = "www" $port = 80 def header(f, host) f.print "GET / HTTP/1.1\r\n" f.print "Host: #{host}\r\n" f.print "Keep-Alive: 300\r\n" f.print "Connection: keep-alive\r\n" end def main s = TCPSocket.new($host, $port) # client -> server t1 = Net::NTLM::Message::Type1.new() header(s, $host) s.print "Authorization: NTLM " + t1.encode64 + "\r\n" s.print "\r\n" # server -> client length = 0 while(line = s.gets) if /^WWW-Authenticate: (NTLM|Negotiate) (.+)\r\n/ =~ line msg = $2 end if /^Content-Length: (\d+)\r\n/ =~ line length = $1.to_i end if /^\r\n/ =~ line if length > 0 cont = s.read(length) end break end end t2 = Net::NTLM::Message.decode64(msg) unless $user and $passwd target = t2.target_name target = Net::NTLM::EncodeUtil.decode_utf16le(target) if t2.has_flag?(:UNICODE) puts "Target: #{target}" print "User name: " ($user = $stdin.readline).chomp! print "Password: " ($passwd = $stdin.readline).chomp! end # client -> server, again t3 = t2.response({:user => $user, :password => $passwd}, {:ntlmv2 => true}) header(s, $host) s.print "Authorization: NTLM " + t3.encode64 + "\r\n" s.print "\r\n" # server -> client length = 0 while(line = s.gets) if /^WWW-Authenticate: (NTLM|Negotiate) (.+)\r\n/ =~ line msg = $2 end if /^Content-Length: (\d+)\r\n/ =~ line length = $1.to_i end if /^\r\n/ =~ line if length > 0 p cont = s.read(length) end break end end s.close end main rubyntlm-0.6.1/.rspec0000644000004100000410000000004112775746221014547 0ustar www-datawww-data--format documentation --color rubyntlm-0.6.1/rubyntlm.gemspec0000644000004100000410000000167712775746221016673 0ustar www-datawww-datarequire File.join(File.dirname(__FILE__), 'lib', 'net', 'ntlm', 'version') Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'rubyntlm' s.version = Net::NTLM::VERSION::STRING s.summary = 'Ruby/NTLM library.' s.description = 'Ruby/NTLM provides message creator and parser for the NTLM authentication.' s.authors = ['Kohei Kajimoto','Paul Morton'] s.email = ['koheik@gmail.com','paul.e.morton@gmail.com'] s.homepage = 'https://github.com/winrb/rubyntlm' s.files = `git ls-files`.split($/) s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } s.test_files = s.files.grep(%r{^(test|spec|features)/}) s.require_paths = ["lib"] s.required_ruby_version = '>= 1.8.7' s.license = 'MIT' s.add_development_dependency "pry" s.add_development_dependency "rake" s.add_development_dependency "rspec", ">= 2.11" s.add_development_dependency "simplecov" end rubyntlm-0.6.1/spec/0000755000004100000410000000000012775746221014371 5ustar www-datawww-datarubyntlm-0.6.1/spec/spec_helper.rb0000644000004100000410000000111212775746221017202 0ustar www-datawww-datarequire 'simplecov' require 'pathname' SimpleCov.start do add_filter '/spec/' add_filter '/config/' add_filter '/vendor/' end if ENV["COVERAGE"] require 'rspec' require 'net/ntlm' # add project lib directory to load path spec_pathname = Pathname.new(__FILE__).dirname root_pathname = spec_pathname.join('..').expand_path # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. support_glob = root_pathname.join('spec', 'support', '**', '*.rb') Dir.glob(support_glob) do |path| require path end rubyntlm-0.6.1/spec/lib/0000755000004100000410000000000012775746221015137 5ustar www-datawww-datarubyntlm-0.6.1/spec/lib/net/0000755000004100000410000000000012775746221015725 5ustar www-datawww-datarubyntlm-0.6.1/spec/lib/net/ntlm_spec.rb0000644000004100000410000001073212775746221020241 0ustar www-datawww-datarequire "spec_helper" describe Net::NTLM do let(:passwd) {"SecREt01"} let(:user) {"user"} let(:domain) {"DOMAIN"} let(:challenge) {["0123456789abcdef"].pack("H*")} let(:client_ch) {["ffffff0011223344"].pack("H*")} let(:timestamp) {1055844000} let(:trgt_info) {[ "02000c0044004f004d00410049004e00" + "01000c00530045005200560045005200" + "0400140064006f006d00610069006e00" + "2e0063006f006d000300220073006500" + "72007600650072002e0064006f006d00" + "610069006e002e0063006f006d000000" + "0000" ].pack("H*")} let(:padded_pwd) { passwd.upcase.ljust(14, "\0")} let(:keys) { Net::NTLM.gen_keys(padded_pwd)} it 'should convert a value to 64-bit LE Integer' do expect(Net::NTLM.pack_int64le(42)).to eq("\x2A\x00\x00\x00\x00\x00\x00\x00") end it 'should split a string into an array of slices, 7 chars or less' do expect(Net::NTLM.split7("HelloWorld!")).to eq([ 'HelloWo', 'rld!']) end it 'should generate DES keys from the supplied string' do first_key = ["52a2516b252a5161"].pack('H*') second_key = ["3180010101010101"].pack('H*') expect(Net::NTLM.gen_keys(padded_pwd)).to eq([first_key, second_key]) end it 'should encrypt the string with DES for each key supplied' do first_crypt = ["ff3750bcc2b22412"].pack('H*') second_crypt = ["c2265b23734e0dac"].pack('H*') expect(Net::NTLM::apply_des(Net::NTLM::LM_MAGIC, keys)).to eq([first_crypt, second_crypt]) end it 'should generate an lm_hash' do expect(Net::NTLM::lm_hash(passwd)).to eq(["ff3750bcc2b22412c2265b23734e0dac"].pack("H*")) end it 'should generate an ntlm_hash' do expect(Net::NTLM::ntlm_hash(passwd)).to eq(["cd06ca7c7e10c99b1d33b7485a2ed808"].pack("H*")) end it 'should generate an ntlmv2_hash' do expect(Net::NTLM::ntlmv2_hash(user, passwd, domain)).to eq(["04b8e0ba74289cc540826bab1dee63ae"].pack("H*")) end context 'when a user passes an NTLM hash for pass-the-hash' do let(:passwd) { Net::NTLM::EncodeUtil.encode_utf16le('ff3750bcc2b22412c2265b23734e0dac:cd06ca7c7e10c99b1d33b7485a2ed808') } it 'should return the correct ntlmv2 hash' do expect(Net::NTLM::ntlmv2_hash(user, passwd, domain)).to eq(["04b8e0ba74289cc540826bab1dee63ae"].pack("H*")) end end it 'should generate an lm_response' do expect(Net::NTLM::lm_response( { :lm_hash => Net::NTLM::lm_hash(passwd), :challenge => challenge } )).to eq(["c337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56"].pack("H*")) end it 'should generate an ntlm_response' do ntlm_hash = Net::NTLM::ntlm_hash(passwd) expect(Net::NTLM::ntlm_response( { :ntlm_hash => ntlm_hash, :challenge => challenge } )).to eq(["25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6"].pack("H*")) end it 'should generate a lvm2_response' do expect(Net::NTLM::lmv2_response( { :ntlmv2_hash => Net::NTLM::ntlmv2_hash(user, passwd, domain), :challenge => challenge }, { :client_challenge => client_ch } )).to eq(["d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"].pack("H*")) end it 'should generate a ntlmv2_response' do expect(Net::NTLM::ntlmv2_response( { :ntlmv2_hash => Net::NTLM::ntlmv2_hash(user, passwd, domain), :challenge => challenge, :target_info => trgt_info }, { :timestamp => timestamp, :client_challenge => client_ch } )).to eq([ "cbabbca713eb795d04c97abc01ee4983" + "01010000000000000090d336b734c301" + "ffffff00112233440000000002000c00" + "44004f004d00410049004e0001000c00" + "53004500520056004500520004001400" + "64006f006d00610069006e002e006300" + "6f006d00030022007300650072007600" + "650072002e0064006f006d0061006900" + "6e002e0063006f006d00000000000000" + "0000" ].pack("H*")) end it 'should generate a ntlm2_session' do session = Net::NTLM::ntlm2_session( { :ntlm_hash => Net::NTLM::ntlm_hash(passwd), :challenge => challenge }, { :client_challenge => client_ch } ) expect(session[0]).to eq(["ffffff001122334400000000000000000000000000000000"].pack("H*")) expect(session[1]).to eq(["10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455"].pack("H*")) end end rubyntlm-0.6.1/spec/lib/net/ntlm/0000755000004100000410000000000012775746221016677 5ustar www-datawww-datarubyntlm-0.6.1/spec/lib/net/ntlm/channel_binding_spec.rb0000644000004100000410000000117512775746221023344 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::ChannelBinding do let(:certificates_path) { 'spec/support/certificates' } let(:sha_256_path) { File.join(certificates_path, 'sha_256_hash.pem') } let(:sha_256_cert) { OpenSSL::X509::Certificate.new(File.read(sha_256_path)) } let(:cert_hash) { "\x04\x0E\x56\x28\xEC\x4A\x98\x29\x91\x70\x73\x62\x03\x7B\xB2\x3C".force_encoding(Encoding::ASCII_8BIT) } subject { Net::NTLM::ChannelBinding.create(sha_256_cert) } describe '#channel_binding_token' do it 'returns the correct hash' do expect(subject.channel_binding_token).to eq cert_hash end end end rubyntlm-0.6.1/spec/lib/net/ntlm/int32_le_spec.rb0000644000004100000410000000065512775746221021663 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Int32LE do int_values = { :default => 252716124, :default_hex => "\x5C\x24\x10\x0f", :alt => 235938908, :alt_hex => "\x5C\x24\x10\x0e", :small => "\x0F\x00", :size => 4, :bits => 32 } it_behaves_like 'a field', 252716124, false it_behaves_like 'an integer field', int_values endrubyntlm-0.6.1/spec/lib/net/ntlm/blob_spec.rb0000644000004100000410000000140412775746221021153 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Blob do fields = [ { :name => :blob_signature, :class => Net::NTLM::Int32LE, :value => 257, :active => true }, { :name => :reserved, :class => Net::NTLM::Int32LE, :value => 0, :active => true }, { :name => :timestamp, :class => Net::NTLM::Int64LE, :value => 0, :active => true }, { :name => :challenge, :class => Net::NTLM::String, :value => '', :active => true }, { :name => :unknown1, :class => Net::NTLM::Int32LE, :value => 0, :active => true }, { :name => :target_info, :class => Net::NTLM::String, :value => '', :active => true }, { :name => :unknown2, :class => Net::NTLM::Int32LE, :value => 0, :active => true }, ] it_behaves_like 'a fieldset', fields end rubyntlm-0.6.1/spec/lib/net/ntlm/int16_le_spec.rb0000644000004100000410000000056412775746221021664 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Int16LE do int_values = { :default => 15, :default_hex => "\x0F\x00", :alt => 14, :alt_hex => "\x0E\x00", :small => "\x0F", :size => 2, :bits => 16 } it_behaves_like 'a field', 15, false it_behaves_like 'an integer field', int_values endrubyntlm-0.6.1/spec/lib/net/ntlm/version_spec.rb0000644000004100000410000000150612775746221021725 0ustar www-datawww-datarequire 'spec_helper' require File.expand_path("#{File.dirname(__FILE__)}/../../../../lib/net/ntlm/version") describe Net::NTLM::VERSION do it 'should contain an integer value for Major Version' do expect(Net::NTLM::VERSION::MAJOR).to be_an Integer end it 'should contain an integer value for Minor Version' do expect(Net::NTLM::VERSION::MINOR).to be_an Integer end it 'should contain an integer value for Patch Version' do expect(Net::NTLM::VERSION::TINY).to be_an Integer end it 'should contain an aggregate version string' do string = [ Net::NTLM::VERSION::MAJOR, Net::NTLM::VERSION::MINOR, Net::NTLM::VERSION::TINY ].join('.') expect(Net::NTLM::VERSION::STRING).to be_a String expect(Net::NTLM::VERSION::STRING).to eq(string) end end rubyntlm-0.6.1/spec/lib/net/ntlm/message/0000755000004100000410000000000012775746221020323 5ustar www-datawww-datarubyntlm-0.6.1/spec/lib/net/ntlm/message/type0_spec.rb0000644000004100000410000000074612775746221022732 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Message::Type0 do fields = [ { :name => :sign, :class => Net::NTLM::String, :value => Net::NTLM::SSP_SIGN, :active => true }, { :name => :type, :class => Net::NTLM::Int32LE, :value => 0, :active => true }, ] flags = [ :UNICODE, :OEM, :REQUEST_TARGET, :NTLM, :ALWAYS_SIGN, :NTLM2_KEY ] it_behaves_like 'a fieldset', fields it_behaves_like 'a message', flags endrubyntlm-0.6.1/spec/lib/net/ntlm/message/type1_spec.rb0000644000004100000410000001123012775746221022721 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Message::Type1 do fields = [ { :name => :sign, :class => Net::NTLM::String, :value => Net::NTLM::SSP_SIGN, :active => true }, { :name => :type, :class => Net::NTLM::Int32LE, :value => 1, :active => true }, { :name => :flag, :class => Net::NTLM::Int32LE, :value => Net::NTLM::DEFAULT_FLAGS[:TYPE1], :active => true }, { :name => :domain, :class => Net::NTLM::SecurityBuffer, :value => '', :active => true }, { :name => :workstation, :class => Net::NTLM::SecurityBuffer, :value => Socket.gethostname, :active => true }, { :name => :os_version, :class => Net::NTLM::String, :value => '', :active => false }, ] flags = [ :UNICODE, :OEM, :REQUEST_TARGET, :NTLM, :ALWAYS_SIGN, :NTLM2_KEY ] it_behaves_like 'a fieldset', fields it_behaves_like 'a message', flags let(:type1_packet) {"TlRMTVNTUAABAAAAB4IIAAAAAAAgAAAAAAAAACAAAAA="} it 'should deserialize' do t1 = Net::NTLM::Message.decode64(type1_packet) expect(t1.class).to eq(Net::NTLM::Message::Type1) expect(t1.domain).to eq('') expect(t1.flag).to eq(557575) expect(t1.os_version).to eq('') expect(t1.sign).to eq("NTLMSSP\0") expect(t1.type).to eq(1) expect(t1.workstation).to eq('') end it 'should serialize' do t1 = Net::NTLM::Message::Type1.new t1.workstation = '' expect(t1.encode64).to eq(type1_packet) end describe '.parse' do subject(:message) { described_class.parse(data) } # http://davenport.sourceforge.net/ntlm.html#appendixC7 context 'NTLM2 Session Response Authentication; NTLM2 Signing and Sealing Using the 128-bit NTLM2 Session Response User Session Key With Key Exchange Negotiated' do let(:data) do ['4e544c4d5353500001000000b78208e000000000000000000000000000000000'].pack('H*') end it 'should set the magic' do expect(message.sign).to eql(Net::NTLM::SSP_SIGN) end it 'should set the type' do expect(message.type).to eq(1) end it 'should set the flags' do expect(message.flag).to eq(0xe00882b7) expect(message).to have_flag(:UNICODE) expect(message).to have_flag(:OEM) expect(message).to have_flag(:REQUEST_TARGET) expect(message).to have_flag(:SIGN) expect(message).to have_flag(:SEAL) expect(message).to have_flag(:NTLM) expect(message).to have_flag(:ALWAYS_SIGN) expect(message).to have_flag(:NTLM2_KEY) expect(message).to have_flag(:KEY128) expect(message).to have_flag(:KEY_EXCHANGE) expect(message).to have_flag(:KEY56) end it 'should have empty workstation' do expect(message.workstation).to be_empty end it 'should have empty domain' do expect(message.domain).to be_empty end end # http://davenport.sourceforge.net/ntlm.html#appendixC9 context 'NTLMv2 Authentication; NTLM1 Signing and Sealing Using the 40-bit NTLMv2 User Session Key' do let(:data) { ['4e544c4d53535000010000003782000000000000000000000000000000000000'].pack('H*') } it 'should set the magic' do expect(message.sign).to eql(Net::NTLM::SSP_SIGN) end it 'should set the type' do expect(message.type).to eq(1) end it 'should set the flags' do expect(message.flag).to eq(0x00008237) expect(message).to have_flag(:UNICODE) expect(message).to have_flag(:OEM) expect(message).to have_flag(:REQUEST_TARGET) expect(message).to have_flag(:SIGN) expect(message).to have_flag(:SEAL) expect(message).to have_flag(:NTLM) expect(message).to have_flag(:ALWAYS_SIGN) end it 'should have empty workstation' do expect(message.workstation).to be_empty end it 'should have empty domain' do expect(message.domain).to be_empty end end context 'NTLMv2 with OS version' do let(:data) { ['4e544c4d5353500001000000978208e2000000000000000000000000000000000602f0230000000f'].pack('H*') } it 'should set the magic' do expect(message.sign).to eql(Net::NTLM::SSP_SIGN) end it 'should set the type' do expect(message.type).to eq(1) end it 'should have empty workstation' do expect(message.workstation).to be_empty end it 'should have empty domain' do expect(message.domain).to be_empty end it 'should set OS version info' do expect(message.os_version).to eq(['0602f0230000000f'].pack('H*')) end end end end rubyntlm-0.6.1/spec/lib/net/ntlm/message/type2_spec.rb0000644000004100000410000001352612775746221022734 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' describe Net::NTLM::Message::Type2 do fields = [ { :name => :sign, :class => Net::NTLM::String, :value => Net::NTLM::SSP_SIGN, :active => true }, { :name => :type, :class => Net::NTLM::Int32LE, :value => 2, :active => true }, { :name => :challenge, :class => Net::NTLM::Int64LE, :value => 0, :active => true }, { :name => :context, :class => Net::NTLM::Int64LE, :value => 0, :active => false }, { :name => :flag, :class => Net::NTLM::Int32LE, :value => Net::NTLM::DEFAULT_FLAGS[:TYPE2], :active => true }, { :name => :target_name, :class => Net::NTLM::SecurityBuffer, :value => '', :active => true }, { :name => :target_info, :class => Net::NTLM::SecurityBuffer, :value => '', :active => false }, { :name => :os_version, :class => Net::NTLM::String, :value => '', :active => false }, ] flags = [ :UNICODE ] it_behaves_like 'a fieldset', fields it_behaves_like 'a message', flags let(:type2_packet) {"TlRMTVNTUAACAAAAHAAcADgAAAAFgooCJ+UA1//+ZM4AAAAAAAAAAJAAkABUAAAABgGxHQAAAA9WAEEARwBSAEEATgBUAC0AMgAwADAAOABSADIAAgAcAFYAQQBHAFIAQQBOAFQALQAyADAAMAA4AFIAMgABABwAVgBBAEcAUgBBAE4AVAAtADIAMAAwADgAUgAyAAQAHAB2AGEAZwByAGEAbgB0AC0AMgAwADAAOABSADIAAwAcAHYAYQBnAHIAYQBuAHQALQAyADAAMAA4AFIAMgAHAAgAZBMdFHQnzgEAAAAA"} let(:type3_packet) {"TlRMTVNTUAADAAAAGAAYAEQAAADAAMAAXAAAAAAAAAAcAQAADgAOABwBAAAUABQAKgEAAAAAAAA+AQAABYKKAgAAAADVS27TfQGmWxSSbXmolTUQyxJmD8ISQuBKKHFKC8GksUZISYc8Ps9RAQEAAAAAAAAANasTdCfOAcsSZg/CEkLgAAAAAAIAHABWAEEARwBSAEEATgBUAC0AMgAwADAAOABSADIAAQAcAFYAQQBHAFIAQQBOAFQALQAyADAAMAA4AFIAMgAEABwAdgBhAGcAcgBhAG4AdAAtADIAMAAwADgAUgAyAAMAHAB2AGEAZwByAGEAbgB0AC0AMgAwADAAOABSADIABwAIAGQTHRR0J84BAAAAAAAAAAB2AGEAZwByAGEAbgB0AGsAbwBiAGUALgBsAG8AYwBhAGwA"} it 'should deserialize' do t2 = Net::NTLM::Message.decode64(type2_packet) expect(t2.class).to eq(Net::NTLM::Message::Type2) expect(t2.challenge).to eq(14872292244261496103) expect(t2.context).to eq(0) expect(t2.flag).to eq(42631685) expect(t2.os_version).to eq(['0601b11d0000000f'].pack('H*')) expect(t2.sign).to eq("NTLMSSP\0") t2_target_info = Net::NTLM::EncodeUtil.decode_utf16le(t2.target_info) if RUBY_VERSION == "1.8.7" expect(t2_target_info).to eq("\x02\x1CVAGRANT-2008R2\x01\x1CVAGRANT-2008R2\x04\x1Cvagrant-2008R2\x03\x1Cvagrant-2008R2\a\b\e$(D+&\e(B\0\0") else expect(t2_target_info).to eq("\u0002\u001CVAGRANT-2008R2\u0001\u001CVAGRANT-2008R2\u0004\u001Cvagrant-2008R2\u0003\u001Cvagrant-2008R2\a\b፤ᐝ❴ǎ\0\0") end expect(Net::NTLM::EncodeUtil.decode_utf16le(t2.target_name)).to eq("VAGRANT-2008R2") expect(t2.type).to eq(2) end it 'should serialize' do source = Net::NTLM::Message.decode64(type2_packet) t2 = Net::NTLM::Message::Type2.new t2.challenge = source.challenge t2.context = source.context t2.flag = source.flag t2.os_version = source.os_version t2.sign = source.sign t2.target_info = source.target_info t2.target_name = source.target_name t2.type = source.type t2.enable(:context) t2.enable(:target_info) t2.enable(:os_version) expect(t2.encode64).to eq(type2_packet) end it 'should generate a type 3 response' do t2 = Net::NTLM::Message.decode64(type2_packet) type3_known = Net::NTLM::Message.decode64(type3_packet) type3_known.flag = 0x028a8205 type3_known.enable(:session_key) type3_known.enable(:flag) t3 = t2.response({:user => 'vagrant', :password => 'vagrant', :domain => ''}, {:ntlmv2 => true, :workstation => 'kobe.local'}) expect(t3.domain).to eq(type3_known.domain) expect(t3.flag).to eq(type3_known.flag) expect(t3.sign).to eq("NTLMSSP\0") expect(t3.workstation).to eq("k\0o\0b\0e\0.\0l\0o\0c\0a\0l\0") expect(t3.user).to eq("v\0a\0g\0r\0a\0n\0t\0") expect(t3.session_key).to eq('') end it 'should upcase domain when provided' do t2 = Net::NTLM::Message.decode64(type2_packet) t3 = t2.response({:user => 'vagrant', :password => 'vagrant', :domain => 'domain'}, {:ntlmv2 => true, :workstation => 'kobe.local'}) expect(t3.domain).to eq("D\0O\0M\0A\0I\0N\0") end describe '.parse' do subject(:message) { described_class.parse(data) } # http://davenport.sourceforge.net/ntlm.html#appendixC7 context 'NTLM2 Session Response Authentication; NTLM2 Signing and Sealing Using the 128-bit NTLM2 Session Response User Session Key With Key Exchange Negotiated' do let(:data) do [ '4e544c4d53535000020000000c000c0030000000358289e0677f1c557a5ee96c' \ '0000000000000000460046003c00000054004500530054004e00540002000c00' \ '54004500530054004e00540001000c004d0045004d0042004500520003001e00' \ '6d0065006d006200650072002e0074006500730074002e0063006f006d000000' \ '0000' ].pack('H*') end it 'should set the magic' do expect(message.sign).to eql(Net::NTLM::SSP_SIGN) end it 'should set the type' do expect(message.type).to eq(2) end it 'should set the target name' do # TESTNT expect(message.target_name).to eq(["54004500530054004e005400"].pack('H*')) end it 'should set the flags' do expect(message.flag).to eq(0xe0898235) end it 'should set the challenge' do expect(message.challenge).to eq(0x6ce95e7a551c7f67) end it 'should set an empty context' do expect(message.context).to be_zero end it 'should set target info' do ti = [ '02000c0054004500530054004e00540001000c004d0045004d00420045005200' \ '03001e006d0065006d006200650072002e0074006500730074002e0063006f00' \ '6d0000000000' ].pack('H*') expect(message.target_info).to eq(ti) end end end end rubyntlm-0.6.1/spec/lib/net/ntlm/message/type3_spec.rb0000644000004100000410000002206612775746221022734 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Message::Type3 do fields = [ { :name => :sign, :class => Net::NTLM::String, :value => Net::NTLM::SSP_SIGN, :active => true }, { :name => :type, :class => Net::NTLM::Int32LE, :value => 3, :active => true }, { :name => :lm_response, :class => Net::NTLM::SecurityBuffer, :value => '', :active => true }, { :name => :ntlm_response, :class => Net::NTLM::SecurityBuffer, :value => '', :active => true }, { :name => :domain, :class => Net::NTLM::SecurityBuffer, :value => '', :active => true }, { :name => :user, :class => Net::NTLM::SecurityBuffer, :value => '', :active => true }, { :name => :workstation, :class => Net::NTLM::SecurityBuffer, :value => '', :active => true }, { :name => :session_key, :class => Net::NTLM::SecurityBuffer, :value => '', :active => false }, { :name => :flag, :class => Net::NTLM::Int32LE, :value => 0, :active => false }, ] flags = [] it_behaves_like 'a fieldset', fields it_behaves_like 'a message', flags describe '.parse' do subject(:message) { described_class.parse(data) } context 'with NTLMv2 data' do let(:data) do # Captured NTLMSSP blob from smbclient with username 'administrator' # and a blank password, i.e.: # smbclient -U 'administrator%' -L //192.168.100.140/ [ '4e544c4d53535000030000001800180040000000c400c4005800000012001200' \ '1c0100001a001a002e0100001a001a0048010000100010006201000015820860' \ 'ced203d860b80c7350050754b238202a8c1c63134f0ae0f086a3fb147e8b2f9f' \ 'de3ef1b1b43c83dc010100000000000080512dba020ed0011c5bc2c8339fd29a' \ '0000000002001e00570049004e002d00420035004a004e003300520048004700' \ '46003300310001001e00570049004e002d00420035004a004e00330052004800' \ '470046003300310004001e00570049004e002d00420035004a004e0033005200' \ '4800470046003300310003001e00570049004e002d00420035004a004e003300' \ '52004800470046003300310007000800a209e5ba020ed0010000000057004f00' \ '52004b00470052004f0055005000610064006d0069006e006900730074007200' \ '610074006f0072004100550053002d004c004500450054002d00310030003300' \ '31007036615cd6d9b19a685ded4312311cd7' ].pack('H*') end let(:server_challenge) { ['f588469dc96fe809'].pack('H*') } it 'should set the magic' do expect(message.sign).to eql(Net::NTLM::SSP_SIGN) end it 'should set the type' do expect(message.type).to eq(3) end it 'should set the LM response' do lm_response = ['ced203d860b80c7350050754b238202a8c1c63134f0ae0f0'].pack('H*') expect(message.lm_response).to eq(lm_response) end it 'should set the NTLM response' do ntlm_response = [ '86a3fb147e8b2f9fde3ef1b1b43c83dc010100000000000080512dba020ed001' \ '1c5bc2c8339fd29a0000000002001e00570049004e002d00420035004a004e00' \ '330052004800470046003300310001001e00570049004e002d00420035004a00' \ '4e00330052004800470046003300310004001e00570049004e002d0042003500' \ '4a004e00330052004800470046003300310003001e00570049004e002d004200' \ '35004a004e00330052004800470046003300310007000800a209e5ba020ed001' \ '00000000' ].pack('H*') expect(message.ntlm_response).to eq(ntlm_response) end it 'should set the user' do # administrator user = ['610064006d0069006e006900730074007200610074006f007200'].pack('H*') expect(message.user).to eq(user) end it 'should set the domain' do # WORKGROUP domain = ['57004f0052004b00470052004f0055005000'].pack('H*') expect(message.domain).to eq(domain) end it 'should set the workstation' do # AUS-LEET-1031 workstation = ['4100550053002d004c004500450054002d003100300033003100'].pack('H*') expect(message.workstation).to eq(workstation) end it 'should set the session key' do session_key = ['7036615cd6d9b19a685ded4312311cd7'].pack('H*') expect(message.session_key).to eq(session_key) end it 'should set the flags' do expect(message.flag).to eq(0x60088215) end it 'should NOT set the OS version structure' do expect(message.os_version).to be_nil end describe '#blank_password?' do it 'should be true' do expect(message.blank_password?(server_challenge)).to be true end end describe '#ntlm_version' do let(:ver) { message.ntlm_version } it 'should be :ntlmv2' do expect(ver).to eq(:ntlmv2) end end end # http://davenport.sourceforge.net/ntlm.html#appendixC7 context 'NTLM2 Session Response Authentication; NTLM2 Signing and Sealing Using the 128-bit NTLM2 Session Response User Session Key With Key Exchange Negotiated' do let(:data) do [ '4e544c4d5353500003000000180018006000000018001800780000000c000c00' \ '40000000080008004c0000000c000c00540000001000100090000000358288e0' \ '54004500530054004e00540074006500730074004d0045004d00420045005200' \ '404d1b6f6915258000000000000000000000000000000000ea8cc49f24da157f' \ '13436637f77693d8b992d619e584c7ee727a5240822ec7af4e9100c43e6fee7f' ].pack('H*') end it 'should set the LM response' do lm_response = ['404d1b6f6915258000000000000000000000000000000000'].pack('H*') expect(message.lm_response).to eq(lm_response) end it 'should set the NTLM response' do ntlm_response = [ 'ea8cc49f24da157f13436637f77693d8b992d619e584c7ee' ].pack('H*') expect(message.ntlm_response).to eq(ntlm_response) end it 'should set the domain' do # TESTNT domain = ['54004500530054004e005400'].pack('H*') expect(message.domain).to eq(domain) end it 'should set the user' do # test user = ['7400650073007400'].pack('H*') expect(message.user).to eq(user) end it 'should set the workstation' do # MEMBER workstation = ['4d0045004d00420045005200'].pack('H*') expect(message.workstation).to eq(workstation) end it 'should set the session key' do session_key = ['727a5240822ec7af4e9100c43e6fee7f'].pack('H*') expect(message.session_key).to eq(session_key) end let(:server_challenge) { ['677f1c557a5ee96c'].pack('H*') } describe '#password?' do it 'should be true for "test1234"' do expect(message.password?('test1234', server_challenge)).to be true end end describe '#blank_password?' do it 'should be false' do expect(message.blank_password?(server_challenge)).to be false end end describe '#ntlm_version' do let(:ver) { message.ntlm_version } it 'should be :ntlm2_session' do expect(ver).to eq(:ntlm2_session) end end end # http://davenport.sourceforge.net/ntlm.html#appendixC9 context 'NTLMv2 Authentication; NTLM1 Signing and Sealing Using the 40-bit NTLMv2 User Session Key' do let(:data) do [ '4e544c4d5353500003000000180018006000000076007600780000000c000c00' \ '40000000080008004c0000000c000c005400000000000000ee00000035828000' \ '54004500530054004e00540074006500730074004d0045004d00420045005200' \ '5d55a02b60a40526ac9a1e4d15fa45a0f2e6329726c598e8f77c67dad00b9321' \ '6242b197fe6addfa0101000000000000502db638677bc301f2e6329726c598e8' \ '0000000002000c0054004500530054004e00540001000c004d0045004d004200' \ '4500520003001e006d0065006d006200650072002e0074006500730074002e00' \ '63006f006d000000000000000000' ].pack 'H*' end it 'should set the NTLM response' do ntlm_response = [ 'f77c67dad00b93216242b197fe6addfa0101000000000000502db638677bc301' \ 'f2e6329726c598e80000000002000c0054004500530054004e00540001000c00' \ '4d0045004d0042004500520003001e006d0065006d006200650072002e007400' \ '6500730074002e0063006f006d000000000000000000' ].pack 'H*' expect(message.ntlm_response).to eq(ntlm_response) end it 'should set the domain' do # TESTNT domain = ['54004500530054004e005400'].pack('H*') expect(message.domain).to eq(domain) end it 'should set the user' do # test user = ['7400650073007400'].pack('H*') expect(message.user).to eq(user) end it 'should set the workstation' do # MEMBER workstation = ['4d0045004d00420045005200'].pack('H*') expect(message.workstation).to eq(workstation) end describe '#ntlm_version' do let(:ver) { message.ntlm_version } it 'should be :ntlmv2' do expect(ver).to eq(:ntlmv2) end end end end end rubyntlm-0.6.1/spec/lib/net/ntlm/target_info_spec.rb0000644000004100000410000000377412775746221022552 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::TargetInfo do let(:key1) { Net::NTLM::TargetInfo::MSV_AV_NB_COMPUTER_NAME } let(:value1) { 'some data' } let(:key2) { Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME } let(:value2) { 'some other data' } let(:data) do dt = key1.dup dt << [value1.length].pack('S') dt << value1 dt << key2.dup dt << [value2.length].pack('S') dt << value2 dt << Net::NTLM::TargetInfo::MSV_AV_EOL dt << [0].pack('S') dt.force_encoding(Encoding::ASCII_8BIT) end subject { Net::NTLM::TargetInfo.new(data) } describe 'invalid data' do context 'invalid pair id' do let(:data) { "\xFF\x00" } it 'returns an error' do expect{subject}.to raise_error Net::NTLM::InvalidTargetDataError end end end describe '#av_pairs' do it 'returns the pair values with the given keys' do expect(subject.av_pairs[key1]).to eq value1 expect(subject.av_pairs[key2]).to eq value2 end context "target data is nil" do subject { Net::NTLM::TargetInfo.new(nil) } it 'returns the pair values with the given keys' do expect(subject.av_pairs).to be_empty end end end describe '#to_s' do let(:data) do dt = key1.dup dt << [value1.length].pack('S') dt << value1 dt << key2.dup dt << [value2.length].pack('S') dt << value2 dt.force_encoding(Encoding::ASCII_8BIT) end let(:new_key) { Net::NTLM::TargetInfo::MSV_AV_CHANNEL_BINDINGS } let(:new_value) { 'bindings' } let(:new_data) do dt = data dt << new_key dt << [new_value.length].pack('S') dt << new_value dt << Net::NTLM::TargetInfo::MSV_AV_EOL dt << [0].pack('S') dt.force_encoding(Encoding::ASCII_8BIT) end it 'returns bytes with any new data added' do subject.av_pairs[new_key] = new_value expect(subject.to_s).to eq new_data end end end rubyntlm-0.6.1/spec/lib/net/ntlm/field_spec.rb0000644000004100000410000000167012775746221021325 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Field do it_behaves_like 'a field', 'Foo', false context 'with no size specified' do let (:field_without_size) { Net::NTLM::Field.new({ :value => 'Foo', :active => true }) } it 'should set size to 0 if not active' do expect(field_without_size.size).to eq(0) end it 'should return 0 if active but no size specified' do field_without_size.active = true expect(field_without_size.size).to eq(0) end end context 'with a size specified' do let (:field_with_size) { Net::NTLM::Field.new({ :value => 'Foo', :active => true, :size => 100 }) } it 'should return the size provided in the initialize options if active' do expect(field_with_size.size).to eq(100) end it 'should still return 0 if not active' do field_with_size.active = false expect(field_with_size.size).to eq(0) end end end rubyntlm-0.6.1/spec/lib/net/ntlm/client/0000755000004100000410000000000012775746221020155 5ustar www-datawww-datarubyntlm-0.6.1/spec/lib/net/ntlm/client/session_spec.rb0000644000004100000410000000542612775746221023206 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Client::Session do let(:t2_challenge) { Net::NTLM::Message.decode64 "TlRMTVNTUAACAAAADAAMADgAAAA1goriAAyk1DmJUnUAAAAAAAAAAFAAUABEAAAABgLwIwAAAA9TAEUAUgBWAEUAUgACAAwAUwBFAFIAVgBFAFIAAQAMAFMARQBSAFYARQBSAAQADABzAGUAcgB2AGUAcgADAAwAcwBlAHIAdgBlAHIABwAIADd7mrNaB9ABAAAAAA==" } let(:inst) { Net::NTLM::Client::Session.new(nil, t2_challenge) } let(:user_session_key) {["3c4918ff0b33e2603e5d7ceaf34bb7d5"].pack("H*")} let(:client_sign_key) {["f7f97a82ec390f9c903dac4f6aceb132"].pack("H*")} let(:client_seal_key) {["6f0d99535033951cbe499cd1914fe9ee"].pack("H*")} let(:server_sign_key) {["f7f97a82ec390f9c903dac4f6aceb132"].pack("H*")} let(:server_seal_key) {["6f0d99535033951cbe499cd1914fe9ee"].pack("H*")} describe "#sign_message" do it "signs a message and when KEY_EXCHANGE is true" do expect(inst).to receive(:client_sign_key).and_return(client_sign_key) expect(inst).to receive(:client_seal_key).and_return(client_seal_key) expect(inst).to receive(:negotiate_key_exchange?).and_return(true) sm = inst.sign_message("Test Message") str = "01000000b35ccd60c110c52f00000000" expect(sm.unpack("H*")[0]).to eq(str) end end describe "#verify_signature" do it "verifies a message signature" do expect(inst).to receive(:server_sign_key).and_return(server_sign_key) expect(inst).to receive(:server_seal_key).and_return(server_seal_key) expect(inst).to receive(:negotiate_key_exchange?).and_return(true) sig = "01000000b35ccd60c110c52f00000000" sm = inst.verify_signature([sig].pack("H*"), "Test Message") expect(sm).to be true end end describe "#seal_message" do it "should seal the message" do expect(inst).to receive(:client_seal_key).and_return(client_seal_key) emsg = inst.seal_message("rubyntlm") expect(emsg.unpack("H*")[0]).to eq("d7389b9604f6274f") end end describe "#unseal_message" do it "should unseal the message" do expect(inst).to receive(:server_seal_key).and_return(server_seal_key) msg = inst.unseal_message(["d7389b9604f6274f"].pack("H*")) expect(msg).to eq("rubyntlm") end end describe "#exported_session_key" do it "returns a random 16-byte key when negotiate_key_exchange? is true" do expect(inst).to receive(:negotiate_key_exchange?).and_return(true) expect(inst).not_to receive(:user_session_key) inst.exported_session_key end it "returns the user_session_key when negotiate_key_exchange? is false" do expect(inst).to receive(:negotiate_key_exchange?).and_return(false) expect(inst).to receive(:user_session_key).and_return(user_session_key) inst.exported_session_key end end end rubyntlm-0.6.1/spec/lib/net/ntlm/int64_le_spec.rb0000644000004100000410000000101712775746221021661 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Int64LE do int_values = { :default => 5294967295, :default_hex => [5294967295 & 0x00000000ffffffff, 5294967295 >> 32].pack("V2"), :alt => 5294967294, :alt_hex => [5294967294 & 0x00000000ffffffff, 5294967294 >> 32].pack("V2"), :small => "\x5C\x24\x10\x0f", :size => 8, :bits => 64 } it_behaves_like 'a field', 252716124, false it_behaves_like 'an integer field', int_values endrubyntlm-0.6.1/spec/lib/net/ntlm/field_set_spec.rb0000644000004100000410000000156712775746221022205 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::FieldSet do fields = [] it_behaves_like 'a fieldset', fields subject(:fieldset_class) do Class.new(Net::NTLM::FieldSet) end context 'an instance' do subject(:fieldset_object) do fieldset_class.string(:test_string, { :value => 'Test', :active => true, :size => 4}) fieldset_class.string(:test_string2, { :value => 'Foo', :active => true, :size => 3}) fieldset_class.new end it 'should serialize all the fields' do expect(fieldset_object.serialize).to eq('TestFoo') end it 'should parse a string across the fields' do fieldset_object.parse('FooBarBaz') expect(fieldset_object.serialize).to eq('FooBarB') end it 'should return an aggregate size of all the fields' do expect(fieldset_object.size).to eq(7) end end end rubyntlm-0.6.1/spec/lib/net/ntlm/client_spec.rb0000644000004100000410000000517012775746221021517 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Client do let(:inst) { Net::NTLM::Client.new("test", "test01", :workstation => "testhost") } let(:user_session_key) {["3c4918ff0b33e2603e5d7ceaf34bb7d5"].pack("H*")} describe "#init_context" do it "returns a default Type1 message" do t1 = inst.init_context expect(t1).to be_instance_of Net::NTLM::Message::Type1 expect(t1.domain).to eq("") expect(t1.workstation).to eq("testhost") expect(t1).to have_flag(:UNICODE) expect(t1).to have_flag(:OEM) expect(t1).to have_flag(:SIGN) expect(t1).to have_flag(:SEAL) expect(t1).to have_flag(:REQUEST_TARGET) expect(t1).to have_flag(:NTLM) expect(t1).to have_flag(:ALWAYS_SIGN) expect(t1).to have_flag(:NTLM2_KEY) expect(t1).to have_flag(:KEY128) expect(t1).to have_flag(:KEY_EXCHANGE) expect(t1).to have_flag(:KEY56) end it "clears session variable on new init_context" do inst.instance_variable_set :@session, "BADSESSION" expect(inst.session).to eq("BADSESSION") inst.init_context expect(inst.session).to be_nil end it "returns a Type1 message with custom flags" do flags = Net::NTLM::FLAGS[:UNICODE] | Net::NTLM::FLAGS[:REQUEST_TARGET] | Net::NTLM::FLAGS[:NTLM] inst = Net::NTLM::Client.new("test", "test01", :workstation => "testhost", :flags => flags) t1 = inst.init_context expect(t1).to be_instance_of Net::NTLM::Message::Type1 expect(t1.domain).to eq("") expect(t1.workstation).to eq("testhost") expect(t1).to have_flag(:UNICODE) expect(t1).not_to have_flag(:OEM) expect(t1).not_to have_flag(:SIGN) expect(t1).not_to have_flag(:SEAL) expect(t1).to have_flag(:REQUEST_TARGET) expect(t1).to have_flag(:NTLM) expect(t1).not_to have_flag(:ALWAYS_SIGN) expect(t1).not_to have_flag(:NTLM2_KEY) expect(t1).not_to have_flag(:KEY128) expect(t1).not_to have_flag(:KEY_EXCHANGE) expect(t1).not_to have_flag(:KEY56) end it "calls authenticate! when we receive a Challenge Message" do t2_challenge = "TlRMTVNTUAACAAAADAAMADgAAAA1goriAAyk1DmJUnUAAAAAAAAAAFAAUABEAAAABgLwIwAAAA9TAEUAUgBWAEUAUgACAAwAUwBFAFIAVgBFAFIAAQAMAFMARQBSAFYARQBSAAQADABzAGUAcgB2AGUAcgADAAwAcwBlAHIAdgBlAHIABwAIADd7mrNaB9ABAAAAAA==" session = double("session") expect(session).to receive(:authenticate!) expect(Net::NTLM::Client::Session).to receive(:new).with(inst, instance_of(Net::NTLM::Message::Type2), nil).and_return(session) inst.init_context t2_challenge end end end rubyntlm-0.6.1/spec/lib/net/ntlm/message_spec.rb0000644000004100000410000000043212775746221021661 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::Message do fields = [] flags = [ :UNICODE, :OEM, :REQUEST_TARGET, :NTLM, :ALWAYS_SIGN, :NTLM2_KEY ] it_behaves_like 'a fieldset', fields it_behaves_like 'a message', flags endrubyntlm-0.6.1/spec/lib/net/ntlm/encode_util_spec.rb0000644000004100000410000000075312775746221022535 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::EncodeUtil do context '#encode_utf16le' do it 'should convert an ASCII string to UTF' do expect(Net::NTLM::EncodeUtil.encode_utf16le('Test'.encode(::Encoding::ASCII_8BIT).freeze)).to eq("T\x00e\x00s\x00t\x00") end end context '#decode_utf16le' do it 'should convert a UTF string to ASCII' do expect(Net::NTLM::EncodeUtil.decode_utf16le("T\x00e\x00s\x00t\x00".freeze)).to eq('Test') end end end rubyntlm-0.6.1/spec/lib/net/ntlm/security_buffer_spec.rb0000644000004100000410000000365412775746221023446 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::SecurityBuffer do fields = [ { :name => :length, :class => Net::NTLM::Int16LE, :value => 0, :active => true }, { :name => :allocated, :class => Net::NTLM::Int16LE, :value => 0, :active => true }, { :name => :offset, :class => Net::NTLM::Int32LE, :value => 0, :active => true }, ] it_behaves_like 'a fieldset', fields it_behaves_like 'a field', 'WORKSTATION', true subject(:domain_security_buffer) do Net::NTLM::SecurityBuffer.new({ :value => 'WORKSTATION', :active => true }) end context 'when setting the value directly' do before(:each) do domain_security_buffer.value = 'DOMAIN1' end it 'should change the value' do expect(domain_security_buffer.value).to eq('DOMAIN1') end it 'should adjust the length field to the size of the new value' do expect(domain_security_buffer.length).to eq(7) end it 'should adjust the allocated field to the size of the new value' do expect(domain_security_buffer.allocated).to eq(7) end end context '#data_size' do it 'should return the size of the value if active' do expect(domain_security_buffer.data_size).to eq(11) end it 'should return 0 if inactive' do domain_security_buffer.active = false expect(domain_security_buffer.data_size).to eq(0) end end context '#parse' do it 'should read in a properly formatted string' do # Length of the string is 8 length = "\x08\x00" # Space allocated is 8 allocated = "\x08\x00" # The offset that the actual value begins at is also 8 offset = "\x08\x00\x00\x00" string_to_parse = "#{length}#{allocated}#{offset}FooBarBaz" expect(domain_security_buffer.parse(string_to_parse)).to eq(8) expect(domain_security_buffer.value).to eq('FooBarBa') end end end rubyntlm-0.6.1/spec/lib/net/ntlm/string_spec.rb0000644000004100000410000000333712775746221021552 0ustar www-datawww-datarequire 'spec_helper' describe Net::NTLM::String do it_behaves_like 'a field', 'Foo', false let(:active) { Net::NTLM::String.new({ :value => 'Test', :active => true, :size => 4 }) } let(:inactive) { Net::NTLM::String.new({ :value => 'Test', :active => false, :size => 4 }) } context '#serialize' do it 'should return the value when active' do expect(active.serialize).to eq('Test') end it 'should return an empty string when inactive' do expect(inactive.serialize).to eq('') end it 'should coerce non-string values into strings' do active.value = 15 expect(active.serialize).to eq('15') end it 'should return empty string on a nil' do active.value = nil expect(active.serialize).to eq('') end end context '#value=' do it 'should set active to false if it empty' do active.value = '' expect(active.active).to eq(false) end it 'should adjust the size based on the value set' do expect(active.size).to eq(4) active.value = 'Foobar' expect(active.size).to eq(6) end end context '#parse' do it 'should read in a string of the proper size' do expect(active.parse('tseT')).to eq(4) expect(active.value).to eq('tseT') end it 'should not read in a string that is too small' do expect(active.parse('B')).to eq(0) expect(active.value).to eq('Test') end it 'should be able to read from an offset and only for the given size' do expect(active.parse('FooBarBaz',3)).to eq(4) expect(active.value).to eq('BarB') end end end rubyntlm-0.6.1/spec/support/0000755000004100000410000000000012775746221016105 5ustar www-datawww-datarubyntlm-0.6.1/spec/support/shared/0000755000004100000410000000000012775746221017353 5ustar www-datawww-datarubyntlm-0.6.1/spec/support/shared/examples/0000755000004100000410000000000012775746221021171 5ustar www-datawww-datarubyntlm-0.6.1/spec/support/shared/examples/net/0000755000004100000410000000000012775746221021757 5ustar www-datawww-datarubyntlm-0.6.1/spec/support/shared/examples/net/ntlm/0000755000004100000410000000000012775746221022731 5ustar www-datawww-datarubyntlm-0.6.1/spec/support/shared/examples/net/ntlm/field_shared.rb0000644000004100000410000000106312775746221025667 0ustar www-datawww-datashared_examples_for 'a field' do | value, active| subject do described_class.new({ :value => value, :active => active }) end it { should respond_to :active } it { should respond_to :value } it { should respond_to :size } it { should respond_to :parse } it { should respond_to :serialize } it 'should set the value from initialize options' do expect(subject.value).to eq(value) end it 'should set active from initialize options' do expect(subject.active).to eq(active) end end rubyntlm-0.6.1/spec/support/shared/examples/net/ntlm/message_shared.rb0000644000004100000410000000177012775746221026235 0ustar www-datawww-datashared_examples_for 'a message' do |flags| subject(:test_message) do unless described_class.names.include?(:flag) described_class.int32LE(:flag, {:value => Net::NTLM::DEFAULT_FLAGS[:TYPE1] }) end described_class.new end it { should respond_to :has_flag? } it { should respond_to :set_flag } it { should respond_to :dump_flags } it { should respond_to :encode64 } it { should respond_to :decode64 } it { should respond_to :head_size } it { should respond_to :data_size } it { should respond_to :size } it { should respond_to :security_buffers } it { should respond_to :deflag } it { should respond_to :data_edge } flags.each do |flag| it "should be able to check if the #{flag} flag is set" do expect(test_message.has_flag?(flag)).to be(true) end end it 'should be able to set a new flag' do test_message.set_flag(:DOMAIN_SUPPLIED) expect(test_message.has_flag?(:DOMAIN_SUPPLIED)).to be(true) end end rubyntlm-0.6.1/spec/support/shared/examples/net/ntlm/int_shared.rb0000644000004100000410000000241512775746221025400 0ustar www-datawww-datashared_examples_for 'an integer field' do |values| subject do described_class.new({ :value => values[:default], :active => true }) end context '#serialize' do it 'should serialize properly with an integer value' do expect(subject.serialize).to eq(values[:default_hex]) end it 'should raise an Exception for a String' do subject.value = 'A' expect {subject.serialize}.to raise_error end it 'should raise an Exception for Nil' do subject.value = nil expect {subject.serialize}.to raise_error end end context '#parse' do it "should parse a raw #{values[:bits].to_s}-bit integer from a string" do expect(subject.parse(values[:alt_hex])).to eq(values[:size]) expect(subject.value).to eq(values[:alt]) end it "should use an offset to find the #{values[:bits].to_s}-bit integer in the string" do expect(subject.parse("Value:#{values[:alt_hex]}",6)).to eq(values[:size]) expect(subject.value).to eq(values[:alt]) end it 'should return 0 and not change the value if the string is not big enough' do expect(subject.parse(values[:small])).to eq(0) expect(subject.value).to eq(values[:default]) end end end rubyntlm-0.6.1/spec/support/shared/examples/net/ntlm/fieldset_shared.rb0000644000004100000410000001576212775746221026416 0ustar www-datawww-datashared_examples_for 'a fieldset' do |fields| subject(:fieldset_class) do Class.new(described_class) end context 'the class' do it { should respond_to :string } it { should respond_to :int16LE } it { should respond_to :int32LE } it { should respond_to :int64LE } it { should respond_to :security_buffer } it { should respond_to :prototypes } it { should respond_to :names } it { should respond_to :types } it { should respond_to :opts } context 'adding a String Field' do before(:each) do fieldset_class.string(:test_string, { :value => 'Test'}) end it 'should set the prototypes correctly' do expect(fieldset_class.prototypes).to include([:test_string, Net::NTLM::String, {:value=>"Test"}]) end it 'should set the names correctly' do expect(fieldset_class.names).to include(:test_string) end it 'should set the types correctly' do expect(fieldset_class.types).to include(Net::NTLM::String) end it 'should set the opts correctly' do expect(fieldset_class.opts).to include({:value => 'Test'}) end context 'when creating an instance' do let(:fieldset_object) do fieldset_class.new end it 'should have the new accessor' do expect(fieldset_object).to respond_to(:test_string) end it 'should have the correct default value' do expect(fieldset_object.test_string).to eq('Test') end end end context 'adding a Int16LE Field' do before(:each) do fieldset_class.int16LE(:test_int, { :value => 15}) end it 'should set the prototypes correctly' do expect(fieldset_class.prototypes).to include([:test_int, Net::NTLM::Int16LE, {:value=>15}]) end it 'should set the names correctly' do expect(fieldset_class.names).to include(:test_int) end it 'should set the types correctly' do expect(fieldset_class.types).to include(Net::NTLM::Int16LE) end it 'should set the opts correctly' do expect(fieldset_class.opts).to include({:value => 15}) end context 'when creating an instance' do let(:fieldset_object) do fieldset_class.new end it 'should have the new accessor' do expect(fieldset_object).to respond_to(:test_int) end it 'should have the correct default value' do expect(fieldset_object.test_int).to eq(15) end end end context 'adding a Int32LE Field' do before(:each) do fieldset_class.int32LE(:test_int, { :value => 15}) end it 'should set the prototypes correctly' do expect(fieldset_class.prototypes).to include([:test_int, Net::NTLM::Int32LE, {:value=>15}]) end it 'should set the names correctly' do expect(fieldset_class.names).to include(:test_int) end it 'should set the types correctly' do expect(fieldset_class.types).to include(Net::NTLM::Int32LE) end it 'should set the opts correctly' do expect(fieldset_class.opts).to include({:value => 15}) end context 'when creating an instance' do let(:fieldset_object) do fieldset_class.new end it 'should have the new accessor' do expect(fieldset_object).to respond_to(:test_int) end it 'should have the correct default value' do expect(fieldset_object.test_int).to eq(15) end end end context 'adding a Int64LE Field' do before(:each) do fieldset_class.int64LE(:test_int, { :value => 15}) end it 'should set the prototypes correctly' do expect(fieldset_class.prototypes).to include([:test_int, Net::NTLM::Int64LE, {:value=>15}]) end it 'should set the names correctly' do expect(fieldset_class.names).to include(:test_int) end it 'should set the types correctly' do expect(fieldset_class.types).to include(Net::NTLM::Int64LE) end it 'should set the opts correctly' do expect(fieldset_class.opts).to include({:value => 15}) end context 'when creating an instance' do let(:fieldset_object) do fieldset_class.new end it 'should have the new accessor' do expect(fieldset_object).to respond_to(:test_int) end it 'should have the correct default value' do expect(fieldset_object.test_int).to eq(15) end end end context 'adding a SecurityBuffer Field' do before(:each) do fieldset_class.security_buffer(:test_buffer, { :value => 15}) end it 'should set the prototypes correctly' do expect(fieldset_class.prototypes).to include([:test_buffer, Net::NTLM::SecurityBuffer, {:value=>15}]) end it 'should set the names correctly' do expect(fieldset_class.names).to include(:test_buffer) end it 'should set the types correctly' do expect(fieldset_class.types).to include(Net::NTLM::SecurityBuffer) end it 'should set the opts correctly' do expect(fieldset_class.opts).to include({:value => 15}) end context 'when creating an instance' do let(:fieldset_object) do fieldset_class.new end it 'should have the new accessor' do expect(fieldset_object).to respond_to :test_buffer end it 'should have the correct default value' do expect(fieldset_object.test_buffer).to eq(15) end end end end context 'an instance' do subject(:fieldset_object) do # FieldSet Base Class and Message Base Class # have no fields by default and thus cannot be initialized # currently. Clumsy workaround for now. if described_class.names.empty? described_class.string(:test_string, { :value => 'Test', :active => true, :size => 4}) end described_class.new end it { should respond_to :serialize } it { should respond_to :parse } it { should respond_to :size } it { should respond_to :enable } it { should respond_to :disable } context 'fields' do fields.each do |field| it { should respond_to field[:name] } context "#{field[:name]}" do it "should be a #{field[:class].to_s}" do expect(fieldset_object[field[:name]].class).to eq(field[:class]) end it "should have a default value of #{field[:value]}" do expect(fieldset_object[field[:name]].value).to eq(field[:value]) end it "should have active set to #{field[:active]}" do expect(fieldset_object[field[:name]].active).to eq(field[:active]) end end end end end end rubyntlm-0.6.1/spec/support/certificates/0000755000004100000410000000000012775746221020552 5ustar www-datawww-datarubyntlm-0.6.1/spec/support/certificates/sha_256_hash.pem0000644000004100000410000000223212775746221023426 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIIDKjCCAhKgAwIBAgIQV0qwsk2MCoxI2Do7IQ6eQzANBgkqhkiG9w0BAQsFADAa MRgwFgYDVQQDDA8xOTIuMTY4LjEzNy4xNjEwHhcNMTYwMTI3MjIyMzA5WhcNMTcw MTI3MjI0MzA5WjAaMRgwFgYDVQQDDA8xOTIuMTY4LjEzNy4xNjEwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+bGWZQFYjF+bV1WJ1L/MGVNmJR89aJ44Z rKI/IXKFdbn5wjQPWng/DcaHR6xtMXQkc22boe58GK/uzl84ofbRa6qtboa5djdZ 9CGsd4Yf6CnVz4mhKSi+BnLi80ydhIRByxoX5bGcCSW6dixR5XiNMaMKzhCjQ+of TU+PBNt7doXB7p0mO4AZz42v4rorRiPNasETj6wlKhFKCMvPLePTwphCgCQsLvgG NQKtFD7TXvrZwplPSeCPhnzd1vHoZMisMn8ZVQ5dAfSEGGkPkOLO0htbUbdaNMoU DPyo7Bu62Q/dqqo1MNbMYM5Ilw8mxe4drOs9UupH0eMovFhVMO0LAgMBAAGjbDBq MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEw GgYDVR0RBBMwEYIPMTkyLjE2OC4xMzcuMTYxMB0GA1UdDgQWBBSLuqyHonSmdm8m 9R+z2obO/X3/+TANBgkqhkiG9w0BAQsFAAOCAQEAH4pDGBclTHrwF+Bkbfj81ibK E2SJSHbdhSx6YCsR28jXUOESfaik5ZPPMXscJqVc1FPpsU9clPFnGiAf0Kt48gsR twfrRSGgJRv1ZgQyJ4dEQkXbQf2+8uY25Rv4kkFDSvPrE6E9o9Jf9bjqefUYski1 YoYdWzgrh/2qoNhnM34wizZgE1bWYbWA9MlUuWH9q/OBEx9uP/K53SXOR7DRzYcY Kg1Z7hV86nvc0WutjEadgdtvJ7eUlg8vAWZqWo5SIdp69l0OEWUlHiaRsPImS5Hd pX3W8n0wHCxBSntDww7U3SHg6DrYf72taBIQW7xFf63S37yLP4CNss68GqPdyQ== -----END CERTIFICATE----- rubyntlm-0.6.1/.travis.yml0000644000004100000410000000030212775746221015543 0ustar www-datawww-datalanguage: ruby rvm: - 2.1.9 - 2.2.0 - 2.3.1 before_install: - gem update bundler # This prevents testing branches that are created just for PRs branches: only: - master rubyntlm-0.6.1/lib/0000755000004100000410000000000012775746221014205 5ustar www-datawww-datarubyntlm-0.6.1/lib/net/0000755000004100000410000000000012775746221014773 5ustar www-datawww-datarubyntlm-0.6.1/lib/net/ntlm.rb0000644000004100000410000002015212775746221016272 0ustar www-datawww-data# encoding: UTF-8 # # = net/ntlm.rb # # An NTLM Authentication Library for Ruby # # This code is a derivative of "dbf2.rb" written by yrock # and Minero Aoki. You can find original code here: # http://jp.rubyist.net/magazine/?0013-CodeReview # ------------------------------------------------------------- # Copyright (c) 2005,2006 yrock # # # 2006-02-11 refactored by Minero Aoki # ------------------------------------------------------------- # # All protocol information used to write this code stems from # "The NTLM Authentication Protocol" by Eric Glass. The author # would thank to him for this tremendous work and making it # available on the net. # http://davenport.sourceforge.net/ntlm.html # ------------------------------------------------------------- # Copyright (c) 2003 Eric Glass # # ------------------------------------------------------------- # # The author also looked Mozilla-Firefox-1.0.7 source code, # namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and # Jonathan Bastien-Filiatrault's libntlm-ruby. # "http://x2a.org/websvn/filedetails.php? # repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1" # The latter has a minor bug in its separate_keys function. # The third key has to begin from the 14th character of the # input string instead of 13th:) #-- # $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $ #++ require 'base64' require 'openssl' require 'openssl/digest' require 'socket' # Load Order is important here require 'net/ntlm/exceptions' require 'net/ntlm/field' require 'net/ntlm/int16_le' require 'net/ntlm/int32_le' require 'net/ntlm/int64_le' require 'net/ntlm/string' require 'net/ntlm/field_set' require 'net/ntlm/blob' require 'net/ntlm/security_buffer' require 'net/ntlm/message' require 'net/ntlm/message/type0' require 'net/ntlm/message/type1' require 'net/ntlm/message/type2' require 'net/ntlm/message/type3' require 'net/ntlm/encode_util' require 'net/ntlm/client' require 'net/ntlm/channel_binding' require 'net/ntlm/target_info' module Net module NTLM LM_MAGIC = "KGS!@\#$%" TIME_OFFSET = 11644473600 MAX64 = 0xffffffffffffffff class << self # Valid format for LAN Manager hex digest portion: 32 hexadecimal characters. LAN_MANAGER_HEX_DIGEST_REGEXP = /[0-9a-f]{32}/i # Valid format for NT LAN Manager hex digest portion: 32 hexadecimal characters. NT_LAN_MANAGER_HEX_DIGEST_REGEXP = /[0-9a-f]{32}/i # Valid format for an NTLM hash composed of `':'`. DATA_REGEXP = /\A#{LAN_MANAGER_HEX_DIGEST_REGEXP}:#{NT_LAN_MANAGER_HEX_DIGEST_REGEXP}\z/ # Takes a string and determines whether it is a valid NTLM Hash # @param [String] the string to validate # @return [Boolean] whether or not the string is a valid NTLM hash def is_ntlm_hash?(data) decoded_data = data.dup decoded_data = EncodeUtil.decode_utf16le(decoded_data) if DATA_REGEXP.match(decoded_data) true else false end end # Conver the value to a 64-Bit Little Endian Int # @param [String] val The string to convert def pack_int64le(val) [val & 0x00000000ffffffff, val >> 32].pack("V2") end # Builds an array of strings that are 7 characters long # @param [String] str The string to split # @api private def split7(str) s = str.dup until s.empty? (ret ||= []).push s.slice!(0, 7) end ret end # Not sure what this is doing # @param [String] str String to generate keys for # @api private def gen_keys(str) split7(str).map{ |str7| bits = split7(str7.unpack("B*")[0]).inject('')\ {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s } [bits].pack("B*") } end def apply_des(plain, keys) dec = OpenSSL::Cipher::Cipher.new("des-cbc") dec.padding = 0 keys.map {|k| dec.key = k dec.encrypt.update(plain) + dec.final } end # Generates a Lan Manager Hash # @param [String] password The password to base the hash on def lm_hash(password) keys = gen_keys password.upcase.ljust(14, "\0") apply_des(LM_MAGIC, keys).join end # Generate a NTLM Hash # @param [String] password The password to base the hash on # @option opt :unicode (false) Unicode encode the password def ntlm_hash(password, opt = {}) pwd = password.dup unless opt[:unicode] pwd = EncodeUtil.encode_utf16le(pwd) end OpenSSL::Digest::MD4.digest pwd end # Generate a NTLMv2 Hash # @param [String] user The username # @param [String] password The password # @param [String] target The domain or workstation to authenticate to # @option opt :unicode (false) Unicode encode the domain def ntlmv2_hash(user, password, target, opt={}) if is_ntlm_hash? password decoded_password = EncodeUtil.decode_utf16le(password) ntlmhash = [decoded_password.upcase[33,65]].pack('H32') else ntlmhash = ntlm_hash(password, opt) end userdomain = user.upcase + target unless opt[:unicode] userdomain = EncodeUtil.encode_utf16le(userdomain) end OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain) end def lm_response(arg) begin hash = arg[:lm_hash] chal = arg[:challenge] rescue raise ArgumentError end chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer) keys = gen_keys hash.ljust(21, "\0") apply_des(chal, keys).join end def ntlm_response(arg) hash = arg[:ntlm_hash] chal = arg[:challenge] chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer) keys = gen_keys hash.ljust(21, "\0") apply_des(chal, keys).join end def ntlmv2_response(arg, opt = {}) begin key = arg[:ntlmv2_hash] chal = arg[:challenge] ti = arg[:target_info] rescue raise ArgumentError end chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer) if opt[:client_challenge] cc = opt[:client_challenge] else cc = rand(MAX64) end cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer) if opt[:timestamp] ts = opt[:timestamp] else ts = Time.now.to_i end # epoch -> milsec from Jan 1, 1601 ts = 10_000_000 * (ts + TIME_OFFSET) blob = Blob.new blob.timestamp = ts blob.challenge = cc blob.target_info = ti bb = blob.serialize OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb end def lmv2_response(arg, opt = {}) key = arg[:ntlmv2_hash] chal = arg[:challenge] chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer) if opt[:client_challenge] cc = opt[:client_challenge] else cc = rand(MAX64) end cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer) OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc end def ntlm2_session(arg, opt = {}) begin passwd_hash = arg[:ntlm_hash] chal = arg[:challenge] rescue raise ArgumentError end chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer) if opt[:client_challenge] cc = opt[:client_challenge] else cc = rand(MAX64) end cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer) keys = gen_keys(passwd_hash.ljust(21, "\0")) session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8) response = apply_des(session_hash, keys).join [cc.ljust(24, "\0"), response] end end end end rubyntlm-0.6.1/lib/net/ntlm/0000755000004100000410000000000012775746221015745 5ustar www-datawww-datarubyntlm-0.6.1/lib/net/ntlm/int64_le.rb0000644000004100000410000000100212775746221017707 0ustar www-datawww-datamodule Net module NTLM class Int64LE < Field def initialize(opt) super(opt) @size = 8 end def parse(str, offset=0) if @active and str.size >= offset + @size d, u = str.slice(offset, @size).unpack("V2") @value = (u * 0x100000000 + d) @size else 0 end end def serialize [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active end end end endrubyntlm-0.6.1/lib/net/ntlm/target_info.rb0000644000004100000410000000513512775746221020577 0ustar www-datawww-datamodule Net module NTLM # Represents a list of AV_PAIR structures # @see https://msdn.microsoft.com/en-us/library/cc236646.aspx class TargetInfo # Allowed AvId values for an AV_PAIR MSV_AV_EOL = "\x00\x00".freeze MSV_AV_NB_COMPUTER_NAME = "\x01\x00".freeze MSV_AV_NB_DOMAIN_NAME = "\x02\x00".freeze MSV_AV_DNS_COMPUTER_NAME = "\x03\x00".freeze MSV_AV_DNS_DOMAIN_NAME = "\x04\x00".freeze MSV_AV_DNS_TREE_NAME = "\x05\x00".freeze MSV_AV_FLAGS = "\x06\x00".freeze MSV_AV_TIMESTAMP = "\x07\x00".freeze MSV_AV_SINGLE_HOST = "\x08\x00".freeze MSV_AV_TARGET_NAME = "\x09\x00".freeze MSV_AV_CHANNEL_BINDINGS = "\x0A\x00".freeze # @param av_pair_sequence [String] AV_PAIR list from challenge message def initialize(av_pair_sequence) @av_pairs = read_pairs(av_pair_sequence) end attr_reader :av_pairs def to_s result = '' av_pairs.each do |k,v| result << k result << [v.length].pack('S') result << v end result << Net::NTLM::TargetInfo::MSV_AV_EOL result << [0].pack('S') result.force_encoding(Encoding::ASCII_8BIT) end private VALID_PAIR_ID = [ MSV_AV_EOL, MSV_AV_NB_COMPUTER_NAME, MSV_AV_NB_DOMAIN_NAME, MSV_AV_DNS_COMPUTER_NAME, MSV_AV_DNS_DOMAIN_NAME, MSV_AV_DNS_TREE_NAME, MSV_AV_FLAGS, MSV_AV_TIMESTAMP, MSV_AV_SINGLE_HOST, MSV_AV_TARGET_NAME, MSV_AV_CHANNEL_BINDINGS ].freeze def read_pairs(av_pair_sequence) offset = 0 result = {} return result if av_pair_sequence.nil? until offset >= av_pair_sequence.length id = av_pair_sequence[offset..offset+1] unless VALID_PAIR_ID.include?(id) raise Net::NTLM::InvalidTargetDataError.new( "Invalid AvId #{to_hex(id)} in AV_PAIR structure", av_pair_sequence ) end length = av_pair_sequence[offset+2..offset+3].unpack('S')[0].to_i if length > 0 value = av_pair_sequence[offset+4..offset+4+length-1] result[id] = value end offset += 4 + length end result end def to_hex(str) return nil if str.nil? str.bytes.map {|b| '0x' + b.to_s(16).rjust(2,'0').upcase}.join('-') end end end end rubyntlm-0.6.1/lib/net/ntlm/blob.rb0000644000004100000410000000151512775746221017212 0ustar www-datawww-datamodule Net module NTLM BLOB_SIGN = 0x00000101 class Blob < FieldSet int32LE :blob_signature, {:value => BLOB_SIGN} int32LE :reserved, {:value => 0} int64LE :timestamp, {:value => 0} string :challenge, {:value => "", :size => 8} int32LE :unknown1, {:value => 0} string :target_info, {:value => "", :size => 0} int32LE :unknown2, {:value => 0} def parse(str, offset=0) # 28 is the length of all fields before the variable-length # target_info field. if str.size > 28 enable(:target_info) # Grab everything except the last 4 bytes (which will be :unknown2) self[:target_info].value = str[28..-5] end super end end end end rubyntlm-0.6.1/lib/net/ntlm/string.rb0000644000004100000410000000114312775746221017577 0ustar www-datawww-datamodule Net module NTLM class String < Field def initialize(opts) super(opts) @size = opts[:size] end def parse(str, offset=0) if @active and str.size >= offset + @size @value = str[offset, @size] @size else 0 end end def serialize if @active @value.to_s else "" end end def value=(val) @value = val @size = @value.nil? ? 0 : @value.size @active = (@size > 0) end end end endrubyntlm-0.6.1/lib/net/ntlm/int16_le.rb0000644000004100000410000000065112775746221017715 0ustar www-datawww-datamodule Net module NTLM class Int16LE < Field def initialize(opt) super(opt) @size = 2 end def parse(str, offset=0) if @active and str.size >= offset + @size @value = str[offset, @size].unpack("v")[0] @size else 0 end end def serialize [@value].pack("v") end end end endrubyntlm-0.6.1/lib/net/ntlm/message/0000755000004100000410000000000012775746221017371 5ustar www-datawww-datarubyntlm-0.6.1/lib/net/ntlm/message/type0.rb0000644000004100000410000000041112775746221020753 0ustar www-datawww-datamodule Net module NTLM class Message # sub class definitions class Type0 < Message string :sign, {:size => 8, :value => SSP_SIGN} int32LE :type, {:value => 0} end end end end rubyntlm-0.6.1/lib/net/ntlm/message/type2.rb0000644000004100000410000000756712775746221021000 0ustar www-datawww-datamodule Net module NTLM class Message # @private false class Type2 < Message string :sign, { :size => 8, :value => SSP_SIGN } int32LE :type, { :value => 2 } security_buffer :target_name, { :size => 0, :value => "" } int32LE :flag, { :value => DEFAULT_FLAGS[:TYPE2] } int64LE :challenge, { :value => 0} int64LE :context, { :value => 0, :active => false } security_buffer :target_info, { :value => "", :active => false } string :os_version, { :size => 8, :value => "", :active => false } # Generates a Type 3 response based on the Type 2 Information # @return [Type3] # @option arg [String] :username The username to authenticate with # @option arg [String] :password The user's password # @option arg [String] :domain ('') The domain to authenticate to # @option opt [String] :workstation (Socket.gethostname) The name of the calling workstation # @option opt [Boolean] :use_default_target (false) Use the domain supplied by the server in the Type 2 packet # @note An empty :domain option authenticates to the local machine. # @note The :use_default_target has precedence over the :domain option def response(arg, opt = {}) usr = arg[:user] pwd = arg[:password] domain = arg[:domain] ? arg[:domain].upcase : "" if usr.nil? or pwd.nil? raise ArgumentError, "user and password have to be supplied" end if opt[:workstation] ws = opt[:workstation] else ws = Socket.gethostname end if opt[:client_challenge] cc = opt[:client_challenge] else cc = rand(MAX64) end cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer) opt[:client_challenge] = cc if has_flag?(:OEM) and opt[:unicode] usr = NTLM::EncodeUtil.decode_utf16le(usr) pwd = NTLM::EncodeUtil.decode_utf16le(pwd) ws = NTLM::EncodeUtil.decode_utf16le(ws) domain = NTLM::EncodeUtil.decode_utf16le(domain) opt[:unicode] = false end if has_flag?(:UNICODE) and !opt[:unicode] usr = NTLM::EncodeUtil.encode_utf16le(usr) pwd = NTLM::EncodeUtil.encode_utf16le(pwd) ws = NTLM::EncodeUtil.encode_utf16le(ws) domain = NTLM::EncodeUtil.encode_utf16le(domain) opt[:unicode] = true end if opt[:use_default_target] domain = self.target_name end ti = self.target_info chal = self[:challenge].serialize if opt[:ntlmv2] ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, domain, opt), :challenge => chal, :target_info => ti} lm_res = NTLM::lmv2_response(ar, opt) ntlm_res = NTLM::ntlmv2_response(ar, opt) elsif has_flag?(:NTLM2_KEY) ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal} lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt) else ar = {:lm_hash => NTLM::lm_hash(pwd), :challenge => chal} lm_res = NTLM::lm_response(ar) ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal} ntlm_res = NTLM::ntlm_response(ar) end Type3.create({ :lm_response => lm_res, :ntlm_response => ntlm_res, :domain => domain, :user => usr, :workstation => ws, :flag => self.flag }) end end end end end rubyntlm-0.6.1/lib/net/ntlm/message/type3.rb0000644000004100000410000001075512775746221020772 0ustar www-datawww-datamodule Net module NTLM class Message # @private false class Type3 < Message string :sign, {:size => 8, :value => SSP_SIGN} int32LE :type, {:value => 3} security_buffer :lm_response, {:value => ""} security_buffer :ntlm_response, {:value => ""} security_buffer :domain, {:value => ""} security_buffer :user, {:value => ""} security_buffer :workstation, {:value => ""} security_buffer :session_key, {:value => "", :active => false } int32LE :flag, {:value => 0, :active => false } string :os_version, {:size => 8, :active => false } class << Type3 # Builds a Type 3 packet # @note All options must be properly encoded with either unicode or oem encoding # @return [Type3] # @option arg [String] :lm_response The LM hash # @option arg [String] :ntlm_response The NTLM hash # @option arg [String] :domain The domain to authenticate to # @option arg [String] :workstation The name of the calling workstation # @option arg [String] :session_key The session key # @option arg [Integer] :flag Flags for the packet def create(arg, opt ={}) t = new t.lm_response = arg[:lm_response] t.ntlm_response = arg[:ntlm_response] t.domain = arg[:domain] t.user = arg[:user] if arg[:workstation] t.workstation = arg[:workstation] end if arg[:session_key] t.enable(:session_key) t.session_key = arg[:session_key] end if arg[:flag] t.enable(:session_key) t.enable(:flag) t.flag = arg[:flag] end t end end # @param server_challenge (see #password?) def blank_password?(server_challenge) password?('', server_challenge) end # @param password [String] # @param server_challenge [String] The server's {Type2#challenge challenge} from the # {Type2} message for which this object is a response. # @return [true] if +password+ was the password used to generate this # {Type3} message # @return [false] otherwise def password?(password, server_challenge) case ntlm_version when :ntlm2_session ntlm2_session_password?(password, server_challenge) when :ntlmv2 ntlmv2_password?(password, server_challenge) else raise end end # @return [Symbol] def ntlm_version if ntlm_response.size == 24 && lm_response[0,8] != "\x00"*8 && lm_response[8,16] == "\x00"*16 :ntlm2_session elsif ntlm_response.size == 24 :ntlmv1 elsif ntlm_response.size > 24 :ntlmv2 end end private def ntlm2_session_password?(password, server_challenge) hash = ntlm_response _lm, empty_hash = NTLM.ntlm2_session( { :ntlm_hash => NTLM.ntlm_hash(password), :challenge => server_challenge, }, { :client_challenge => lm_response[0,8] } ) hash == empty_hash end def ntlmv2_password?(password, server_challenge) # The first 16 bytes of the ntlm_response are the HMAC of the blob # that follows it. blob = Blob.new blob.parse(ntlm_response[16..-1]) empty_hash = NTLM.ntlmv2_response( { # user and domain came from the serialized data here, so # they're already unicode :ntlmv2_hash => NTLM.ntlmv2_hash(user, '', domain, :unicode => true), :challenge => server_challenge, :target_info => blob.target_info }, { :client_challenge => blob.challenge, # The blob's timestamp is already in milliseconds since 1601, # so convert it back to epoch time first :timestamp => (blob.timestamp / 10_000_000) - NTLM::TIME_OFFSET, } ) empty_hash == ntlm_response end end end end end rubyntlm-0.6.1/lib/net/ntlm/message/type1.rb0000644000004100000410000000105012775746221020754 0ustar www-datawww-datamodule Net module NTLM class Message # @private false class Type1 < Message string :sign, {:size => 8, :value => SSP_SIGN} int32LE :type, {:value => 1} int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] } security_buffer :domain, {:value => ""} security_buffer :workstation, {:value => Socket.gethostname } string :os_version, {:size => 8, :value => "", :active => false } end end end end rubyntlm-0.6.1/lib/net/ntlm/security_buffer.rb0000644000004100000410000000166512775746221021502 0ustar www-datawww-data module Net module NTLM class SecurityBuffer < FieldSet int16LE :length, {:value => 0} int16LE :allocated, {:value => 0} int32LE :offset, {:value => 0} attr_accessor :active def initialize(opts={}) super() @value = opts[:value] @active = opts[:active].nil? ? true : opts[:active] @size = 8 end def parse(str, offset=0) if @active and str.size >= offset + @size super(str, offset) @value = str[self.offset, self.length] @size else 0 end end def serialize super if @active end def value @value end def value=(val) @value = val self.length = self.allocated = val.size end def data_size @active ? @value.size : 0 end end end endrubyntlm-0.6.1/lib/net/ntlm/client.rb0000644000004100000410000000404112775746221017547 0ustar www-datawww-datamodule Net module NTLM class Client DEFAULT_FLAGS = NTLM::FLAGS[:UNICODE] | NTLM::FLAGS[:OEM] | NTLM::FLAGS[:SIGN] | NTLM::FLAGS[:SEAL] | NTLM::FLAGS[:REQUEST_TARGET] | NTLM::FLAGS[:NTLM] | NTLM::FLAGS[:ALWAYS_SIGN] | NTLM::FLAGS[:NTLM2_KEY] | NTLM::FLAGS[:KEY128] | NTLM::FLAGS[:KEY_EXCHANGE] | NTLM::FLAGS[:KEY56] attr_reader :username, :password, :domain, :workstation, :flags # @note All string parameters should be encoded in UTF-8. The proper # final encoding for placing in the various {Message messages} will be # chosen based on negotiation with the server. # # @param username [String] # @param password [String] # @option opts [String] :domain where we're authenticating to # @option opts [String] :workstation local workstation name # @option opts [Fixnum] :flags (DEFAULT_FLAGS) see Net::NTLM::Message::Type1.flag def initialize(username, password, opts = {}) @username = username @password = password @domain = opts[:domain] || nil @workstation = opts[:workstation] || nil @flags = opts[:flags] || DEFAULT_FLAGS end # @return [NTLM::Message] def init_context(resp = nil, channel_binding = nil) if resp.nil? @session = nil type1_message else @session = Client::Session.new(self, Net::NTLM::Message.decode64(resp), channel_binding) @session.authenticate! end end # @return [Client::Session] def session @session end def session_key @session.exported_session_key end private # @return [Message::Type1] def type1_message type1 = Message::Type1.new type1[:flag].value = flags type1.domain = domain if domain type1.workstation = workstation if workstation type1 end end end end require "net/ntlm/client/session" rubyntlm-0.6.1/lib/net/ntlm/client/0000755000004100000410000000000012775746221017223 5ustar www-datawww-datarubyntlm-0.6.1/lib/net/ntlm/client/session.rb0000644000004100000410000001566612775746221021251 0ustar www-datawww-datamodule 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::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::Cipher.new("rc4") rc4.encrypt rc4.key = client_seal_key rc4 end end def server_cipher @server_cipher ||= begin rc4 = OpenSSL::Cipher::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 rubyntlm-0.6.1/lib/net/ntlm/version.rb0000644000004100000410000000026612775746221017763 0ustar www-datawww-datamodule Net module NTLM # @private module VERSION MAJOR = 0 MINOR = 6 TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end end end rubyntlm-0.6.1/lib/net/ntlm/message.rb0000644000004100000410000000623012775746221017717 0ustar www-datawww-datamodule Net module NTLM SSP_SIGN = "NTLMSSP\0" FLAGS = { :UNICODE => 0x00000001, :OEM => 0x00000002, :REQUEST_TARGET => 0x00000004, :MBZ9 => 0x00000008, :SIGN => 0x00000010, :SEAL => 0x00000020, :NEG_DATAGRAM => 0x00000040, :NETWARE => 0x00000100, :NTLM => 0x00000200, :NEG_NT_ONLY => 0x00000400, :MBZ7 => 0x00000800, :DOMAIN_SUPPLIED => 0x00001000, :WORKSTATION_SUPPLIED => 0x00002000, :LOCAL_CALL => 0x00004000, :ALWAYS_SIGN => 0x00008000, :TARGET_TYPE_DOMAIN => 0x00010000, :NTLM2_KEY => 0x00080000, :TARGET_INFO => 0x00800000, :KEY128 => 0x20000000, :KEY_EXCHANGE => 0x40000000, :KEY56 => 0x80000000 }.freeze FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] } DEFAULT_FLAGS = { :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY], :TYPE2 => FLAGS[:UNICODE], :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY] } # @private false class Message < FieldSet class << Message def parse(str) m = Type0.new m.parse(str) case m.type when 1 t = Type1.new.parse(str) when 2 t = Type2.new.parse(str) when 3 t = Type3.new.parse(str) else raise ArgumentError, "unknown type: #{m.type}" end t end def decode64(str) parse(Base64.decode64(str)) end end # @return [self] def parse(str) super while has_disabled_fields? && serialize.size < str.size # enable the next disabled field self.class.names.find { |name| !self[name].active && enable(name) } super end self end def has_flag?(flag) (self[:flag].value & FLAGS[flag]) == FLAGS[flag] end def set_flag(flag) self[:flag].value |= FLAGS[flag] end def dump_flags FLAG_KEYS.each{ |k| print(k, "=", has_flag?(k), "\n") } end def serialize deflag super + security_buffers.map{|n, f| f.value}.join end def encode64 Base64.encode64(serialize).gsub(/\n/, '') end def decode64(str) parse(Base64.decode64(str)) end alias head_size size def data_size security_buffers.inject(0){|sum, a| sum += a[1].data_size} end def size head_size + data_size end def security_buffers @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)} end def deflag security_buffers.inject(head_size){|cur, a| a[1].offset = cur cur += a[1].data_size } end def data_edge security_buffers.map{ |n, f| f.active ? f.offset : size}.min end end end end rubyntlm-0.6.1/lib/net/ntlm/int32_le.rb0000644000004100000410000000067012775746221017714 0ustar www-datawww-datamodule Net module NTLM class Int32LE < Field def initialize(opt) super(opt) @size = 4 end def parse(str, offset=0) if @active and str.size >= offset + @size @value = str.slice(offset, @size).unpack("V")[0] @size else 0 end end def serialize [@value].pack("V") if @active end end end endrubyntlm-0.6.1/lib/net/ntlm/field_set.rb0000644000004100000410000000571612775746221020241 0ustar www-datawww-datamodule Net module NTLM # base class of data structure class FieldSet class << FieldSet # @macro string_security_buffer # @method $1 # @method $1= # @return [String] def string(name, opts) add_field(name, Net::NTLM::String, opts) end # @macro int16le_security_buffer # @method $1 # @method $1= # @return [Int16LE] def int16LE(name, opts) add_field(name, Net::NTLM::Int16LE, opts) end # @macro int32le_security_buffer # @method $1 # @method $1= # @return [Int32LE] def int32LE(name, opts) add_field(name, Net::NTLM::Int32LE, opts) end # @macro int64le_security_buffer # @method $1 # @method $1= # @return [Int64] def int64LE(name, opts) add_field(name, Net::NTLM::Int64LE, opts) end # @macro security_buffer # @method $1 # @method $1= # @return [SecurityBuffer] def security_buffer(name, opts) add_field(name, Net::NTLM::SecurityBuffer, opts) end def prototypes @proto end def names return [] if @proto.nil? @proto.map{|n, t, o| n} end def types return [] if @proto.nil? @proto.map{|n, t, o| t} end def opts return [] if @proto.nil? @proto.map{|n, t, o| o} end private def add_field(name, type, opts) (@proto ||= []).push [name, type, opts] define_accessor name end def define_accessor(name) module_eval(<<-End, __FILE__, __LINE__ + 1) def #{name} self['#{name}'].value end def #{name}=(val) self['#{name}'].value = val end End end end def initialize @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] } end def parse(str, offset=0) @alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)} end def serialize @alist.map{|n, f| f.serialize }.join end def size @alist.inject(0){|sum, a| sum += a[1].size} end def [](name) a = @alist.assoc(name.to_s.intern) raise ArgumentError, "no such field: #{name}" unless a a[1] end def []=(name, val) a = @alist.assoc(name.to_s.intern) raise ArgumentError, "no such field: #{name}" unless a a[1] = val end def enable(name) self[name].active = true end def disable(name) self[name].active = false end def has_disabled_fields? @alist.any? { |name, field| !field.active } end end end end rubyntlm-0.6.1/lib/net/ntlm/field.rb0000644000004100000410000000131412775746221017354 0ustar www-datawww-datamodule Net module NTLM # base classes for primitives # @private class Field attr_accessor :active, :value def initialize(opts) @value = opts[:value] @active = opts[:active].nil? ? true : opts[:active] @size = opts[:size].nil? ? 0 : opts[:size] end def size @active ? @size : 0 end # Serializer function for field data # Exists in this class to be overridden by child classes def serialize raise NotImplementedError end # Parser function for field data # Exists in this class to be overridden by child classes def parse(str, offset=0) raise NotImplementedError end end end endrubyntlm-0.6.1/lib/net/ntlm/channel_binding.rb0000644000004100000410000000442112775746221021375 0ustar www-datawww-datamodule Net module NTLM class ChannelBinding # Creates a ChannelBinding used for Extended Protection Authentication # @see http://blogs.msdn.com/b/openspecification/archive/2013/03/26/ntlm-and-channel-binding-hash-aka-exteneded-protection-for-authentication.aspx # # @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing # the outer TLS channel # @return [NTLM::ChannelBinding] A ChannelBinding holding a token that can be # embedded in a {Type3} message def self.create(outer_channel) new(outer_channel) end # @param outer_channel [OpenSSL::X509::Certificate] Server certificate securing # the outer TLS channel def initialize(outer_channel) @channel = outer_channel @unique_prefix = 'tls-server-end-point' @initiator_addtype = 0 @initiator_address_length = 0 @acceptor_addrtype = 0 @acceptor_address_length = 0 end attr_reader :channel, :unique_prefix, :initiator_addtype attr_reader :initiator_address_length, :acceptor_addrtype attr_reader :acceptor_address_length # Returns a channel binding hash acceptable for use as a AV_PAIR MsvAvChannelBindings # field value as specified in the NTLM protocol # # @return [String] MD5 hash of gss_channel_bindings_struct def channel_binding_token @channel_binding_token ||= OpenSSL::Digest::MD5.new(gss_channel_bindings_struct).digest end def gss_channel_bindings_struct @gss_channel_bindings_struct ||= begin token = [initiator_addtype].pack('I') token << [initiator_address_length].pack('I') token << [acceptor_addrtype].pack('I') token << [acceptor_address_length].pack('I') token << [application_data.length].pack('I') token << application_data token end end def channel_hash @channel_hash ||= OpenSSL::Digest::SHA256.new(channel.to_der) end def application_data @application_data ||= begin data = unique_prefix data << ':' data << channel_hash.digest data end end end end end rubyntlm-0.6.1/lib/net/ntlm/encode_util.rb0000644000004100000410000000350212775746221020564 0ustar www-datawww-datamodule Net module NTLM class EncodeUtil if RUBY_VERSION == "1.8.7" require "kconv" # Decode a UTF16 string to a ASCII string # @param [String] str The string to convert def self.decode_utf16le(str) Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16) end # Encodes a ASCII string to a UTF16 string # @param [String] str The string to convert def self.encode_utf16le(str) swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII)) end # Taggle the strings endianness between big/little and little/big # @param [String] str The string to swap the endianness on def self.swap16(str) str.unpack("v*").pack("n*") end else # Use native 1.9 string encoding functions # Decode a UTF16 string to a ASCII string # @param [String] str The string to convert def self.decode_utf16le(str) str = str.dup.force_encoding(Encoding::UTF_16LE) str.encode(Encoding::UTF_8, Encoding::UTF_16LE).force_encoding('UTF-8') end # Encodes a ASCII string to a UTF16 string # @param [String] str The string to convert # @note This implementation may seem stupid but the problem is that UTF16-LE and UTF-8 are incompatiable # encodings. This library uses string contatination to build the packet bytes. The end result is that # you can either marshal the encodings elsewhere of simply know that each time you call encode_utf16le # the function will convert the string bytes to UTF-16LE and note the encoding as UTF-8 so that byte # concatination works seamlessly. def self.encode_utf16le(str) str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8') end end end end end rubyntlm-0.6.1/lib/net/ntlm/exceptions.rb0000644000004100000410000000037312775746221020456 0ustar www-datawww-datamodule Net module NTLM class NtlmError < StandardError; end class InvalidTargetDataError < NtlmError attr_reader :data def initialize(msg, data) @data = data super(msg) end end end end rubyntlm-0.6.1/lib/rubyntlm.rb0000644000004100000410000000002212775746221016400 0ustar www-datawww-datarequire 'net/ntlm'rubyntlm-0.6.1/.gitignore0000644000004100000410000000003512775746221015425 0ustar www-datawww-data.yardoc /doc Gemfile.lock rubyntlm-0.6.1/LICENSE0000644000004100000410000000214712775746221014450 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2013 Paul Morton, Matt Zukowski, Kohei Kajimoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.rubyntlm-0.6.1/CHANGELOG.md0000644000004100000410000000057312775746221015255 0ustar www-datawww-data0.2.0 - Major changes to behavior!!!! - Bug - Type 1 packets do not include a domain and workstation by defauly. Packet capture software will see this type of packet as malformed. All packets now include this information - Bug - Type 3 packets do not include the calling workstation. This should be setup by default. 0.1.2 - Feature user can specify the target domain rubyntlm-0.6.1/README.md0000644000004100000410000000214512775746221014720 0ustar www-datawww-data# Ruby/NTLM -- NTLM Authentication Library for Ruby [![Build Status](https://travis-ci.org/WinRb/rubyntlm.png)](https://travis-ci.org/WinRb/rubyntlm) Ruby/NTLM provides message creator and parser for the NTLM authentication. __100% Ruby__ How to install -------------- ```ruby require 'rubyntlm' ``` Simple Example -------------- ### Creating NTLM Type 1 message ```ruby t1 = Net::NTLM::Message::Type1.new() ``` ### Parsing NTLM Type 2 message from server ```ruby t2 = Net::NTLM::Message.parse(message_from_server) ``` ### Creating NTLM Type 3 message ```ruby t3 = t2.response({:user => 'user', :password => 'passwd'}) ``` Support ------- https://groups.google.com/forum/?fromgroups#!forum/rubyntlm Contributing ------------ 1. Fork it. 2. Create a branch (git checkout -b my_feature_branch) 3. Commit your changes (git commit -am "Added a sweet feature") 4. Push to the branch (git push origin my_feature_branch) 5. Create a pull requst from your branch into master (Please be sure to provide enough detail for us to cipher what this change is doing)