Skip to content

Commit

Permalink
Merge pull request #1 from ismo1/Repairing_digest
Browse files Browse the repository at this point in the history
Repairing digest authentication in handle_request devrandom#19
  • Loading branch information
ismo1 authored May 3, 2021
2 parents dd41e51 + eca80d8 commit 9e48683
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 95 deletions.
114 changes: 19 additions & 95 deletions lib/net/dav.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
require 'net/https'
require 'uri'
require 'nokogiri'
require File.dirname(__FILE__) + "/dav/item"
require 'net/dav/item'
require 'base64'
require 'digest/md5'
require File.dirname(__FILE__) + "/dav/version"

begin
require 'curb'
rescue LoadError
Expand Down Expand Up @@ -159,7 +157,11 @@ def handle_request(req, headers, limit = MAX_REDIRECTS, &block)
when :basic
req.basic_auth @user, @pass
when :digest
digest_auth(req, @user, @pass, response)
uri = @uri.merge(req.path)
uri.user = @user
uri.password = @pass
# re-calculate the digest header with the current uri path (and increment the nc)
req['Authorization'] = @digest_info.auth_header(uri, @lastServerDigestHeader, req.method)
end

response = nil
Expand All @@ -179,15 +181,18 @@ def handle_request(req, headers, limit = MAX_REDIRECTS, &block)
return response
when Net::HTTPUnauthorized then
response.error! unless @user
response.error! if req['authorization']
new_req = clone_req(req.path, req, headers)
if response['www-authenticate'] =~ /^basic/i
if response['www-authenticate'] =~ /^Basic/
if disable_basic_auth
raise "server requested basic auth, but that is disabled"
end
response.error! if req['authorization']
@authorization = :basic
else
@authorization = :digest
# Need to set up a new digest auth.
# Either we never had one, or the server wants a fresh one calculated.
digest_auth(req, @user, @pass, response)
end
return handle_request(req, headers, limit - 1, &block)
when Net::HTTPRedirection then
Expand All @@ -213,66 +218,22 @@ def clone_req(path, req, headers)
end
new_req.content_length = req.content_length if req.content_length
headers.each_pair { |key, value| new_req[key] = value } if headers
new_req.content_type = req.content_type if req.content_type
return new_req
end

CNONCE = Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))).slice(0, 8)

def digest_auth(request, user, password, response)
# based on http://segment7.net/projects/ruby/snippets/digest_auth.rb
@nonce_count = 0 if @nonce_count.nil?
@nonce_count += 1

raise "bad www-authenticate header" unless (response['www-authenticate'] =~ /^(\w+) (.*)/)

params = {}
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }

a_1 = "#{user}:#{params['realm']}:#{password}"
a_2 = "#{request.method}:#{request.path}"
request_digest = ''
request_digest << Digest::MD5.hexdigest(a_1)
request_digest << ':' << params['nonce']
request_digest << ':' << ('%08x' % @nonce_count)
request_digest << ':' << CNONCE
request_digest << ':' << params['qop']
request_digest << ':' << Digest::MD5.hexdigest(a_2)

header = []
header << "Digest username=\"#{user}\""
header << "realm=\"#{params['realm']}\""
header << "nonce=\"#{params['nonce']}\""
header << "uri=\"#{request.path}\""
header << "cnonce=\"#{CNONCE}\""
header << "nc=#{'%08x' % @nonce_count}"
header << "qop=#{params['qop']}"
header << "response=\"#{Digest::MD5.hexdigest(request_digest)}\""
header << "algorithm=\"MD5\""

header = header.join(', ')
request['Authorization'] = header
end

def cert_file(cert_file)
# expects a OpenSSL::X509::Certificate object as client certificate
@http.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
#puts @http.cert.not_after
#puts @http.cert.subject
end
@digest_info = Net::HTTP::DigestAuth.new
uri = @uri
uri.user = user
uri.password = password
request['Authorization'] = @digest_info.auth_header(uri, response['www-authenticate'], request.method)

def cert_key(cert_file, cert_file_password)
# expects a OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object
if cert_file_password then
@http.key = OpenSSL::PKey::RSA.new(File.read(cert_file),cert_file_password)
else
@http.key = OpenSSL::PKey::RSA.new(File.read(cert_file))
end
end

# path of a CA certification file in PEM format. The file can contain several CA certificates.
def ca_file(ca_file)
@http.ca_file = ca_file
# Keep this around so that we can send subsequent requests without hitting HTTPUnauthorized again.
@lastServerDigestHeader = response['www-authenticate']
end
end

Expand Down Expand Up @@ -334,23 +295,6 @@ def request_returning_body(verb, path, headers)
end
curl.body_str
end

def cert_file(cert_file)
# expects a cert file
@curl.cert = cert_file
end

def cert_key(cert_file, cert_file_password)
if cert_file_password then
@curl.certpassword = cert_file_password
end
@curl.key = cert_key
end

def ca_file(ca_file)
# path of a cacert bundle for this instance. This file will be used to validate SSL certificates.
@curl.cacert = ca_file
end

end

Expand Down Expand Up @@ -439,33 +383,13 @@ def start(&block) # :yield: dav
def credentials(user, pass)
@handler.user = user
@handler.pass = pass

# Return something explicitly since this command might be run in a
# console where the last statement would be printed.
nil
end

# Set credentials for ssl certificate authentication
def ssl_certificate(cert_file, *cert_file_password)
@handler.cert_file(cert_file)
@handler.cert_key(cert_file, cert_file_password)

# Return something explicitly since this command might be run in a
# console where the last statement would be printed.
nil
end

# Set additional ssl authorities for ssl certificate authentication
def ssl_authority(ca_file)
@handler.ca_file(ca_file)
nil
end

# Set extra headers for the dav request
def headers(headers)
@headers = headers
end

# Perform a PROPFIND request
#
# Example:
Expand Down
9 changes: 9 additions & 0 deletions net_dav.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ Gem::Specification.new do |spec|
spec.description = "WebDAV client library in the style of Net::HTTP, using Net::HTTP and libcurl, if installed"
spec.homepage = "https://github.com/devrandom/net_dav"

# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
# if spec.respond_to?(:metadata)
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
# else
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
# end

spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
Expand All @@ -24,6 +32,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "rspec"

spec.add_dependency "nokogiri"
spec.add_dependency "net-http-digest_auth", ">=1.2"



Expand Down

0 comments on commit 9e48683

Please sign in to comment.