Skip to content

Commit

Permalink
Support for multiple algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
lawrencepit committed Apr 29, 2012
1 parent 9f56778 commit a98e44b
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 23 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The fingerprint to use, if you use the default X.509 certificate of this gem, is
Service Providers
-----------------

To act as a Service Provider which generates SAML Requests use the excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.


Author
Expand Down
3 changes: 2 additions & 1 deletion lib/saml-idp/configurator.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# encoding: utf-8
module SamlIdp
class Configurator
attr_accessor :x509_certificate, :secret_key
attr_accessor :x509_certificate, :secret_key, :algorithm

def initialize(config_file = nil)
self.x509_certificate = Default::X509_CERTIFICATE
self.secret_key = Default::SECRET_KEY
self.algorithm = :sha1
instance_eval(File.read(config_file), config_file) if config_file
end
end
Expand Down
40 changes: 32 additions & 8 deletions lib/saml-idp/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Controller
require 'base64'
require 'time'

attr_accessor :x509_certificate, :secret_key
attr_accessor :x509_certificate, :secret_key, :algorithm
attr_accessor :saml_acs_url

def x509_certificate
Expand All @@ -18,6 +18,30 @@ def secret_key
@secret_key = SamlIdp.config.secret_key
end

def algorithm
return @algorithm if defined?(@algorithm)
self.algorithm = SamlIdp.config.algorithm
@algorithm
end

def algorithm=(algorithm)
@algorithm = algorithm
if algorithm.is_a?(Symbol)
@algorithm = case algorithm
when :sha256 then OpenSSL::Digest::SHA256
when :sha384 then OpenSSL::Digest::SHA384
when :sha512 then OpenSSL::Digest::SHA512
else
OpenSSL::Digest::SHA1
end
end
@algorithm
end

def algorithm_name
algorithm.to_s.split('::').last.downcase
end

protected

def validate_saml_request(saml_request = params[:SAMLRequest])
Expand All @@ -26,11 +50,11 @@ def validate_saml_request(saml_request = params[:SAMLRequest])

def decode_SAMLRequest(saml_request)
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
text = zstream.inflate(Base64.decode64(saml_request))
@saml_request = zstream.inflate(Base64.decode64(saml_request))
zstream.finish
zstream.close
@saml_request_id = text[/ID='(.+?)'/, 1]
@saml_acs_url = text[/AssertionConsumerServiceURL='(.+?)'/, 1]
@saml_request_id = @saml_request[/ID='(.+?)'/, 1]
@saml_acs_url = @saml_request[/AssertionConsumerServiceURL='(.+?)'/, 1]
end

def encode_SAMLResponse(nameID, opts = {})
Expand All @@ -41,11 +65,11 @@ def encode_SAMLResponse(nameID, opts = {})

assertion = %[<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_#{reference_id}" IssueInstant="#{now.iso8601}" Version="2.0"><Issuer>#{issuer_uri}</Issuer><Subject><NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">#{nameID}</NameID><SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData InResponseTo="#{@saml_request_id}" NotOnOrAfter="#{(now+3*60).iso8601}" Recipient="#{@saml_acs_url}"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore="#{(now-5).iso8601}" NotOnOrAfter="#{(now+60*60).iso8601}"><AudienceRestriction><Audience>#{audience_uri}</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"><AttributeValue>#{nameID}</AttributeValue></Attribute></AttributeStatement><AuthnStatement AuthnInstant="#{now.iso8601}" SessionIndex="_#{reference_id}"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion>]

digest_value = Base64.encode64(OpenSSL::Digest::SHA1.digest(assertion)).chomp
digest_value = Base64.encode64(algorithm.digest(assertion)).gsub(/\n/, '')

signed_info = %[<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod><ds:Reference URI="#_#{reference_id}"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod><ds:DigestValue>#{digest_value}</ds:DigestValue></ds:Reference></ds:SignedInfo>]
signed_info = %[<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-#{algorithm_name}"></ds:SignatureMethod><ds:Reference URI="#_#{reference_id}"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig##{algorithm_name}"></ds:DigestMethod><ds:DigestValue>#{digest_value}</ds:DigestValue></ds:Reference></ds:SignedInfo>]

signature_value = sign(signed_info)
signature_value = sign(signed_info).gsub(/\n/, '')

signature = %[<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">#{signed_info}<ds:SignatureValue>#{signature_value}</ds:SignatureValue><KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>#{self.x509_certificate}</ds:X509Certificate></ds:X509Data></KeyInfo></ds:Signature>]

Expand All @@ -60,7 +84,7 @@ def encode_SAMLResponse(nameID, opts = {})

def sign(data)
key = OpenSSL::PKey::RSA.new(self.secret_key)
Base64.encode64(key.sign(OpenSSL::Digest::SHA1.new, data))
Base64.encode64(key.sign(algorithm.new, data))
end

end
Expand Down
1 change: 1 addition & 0 deletions ruby-saml-idp.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ Gem::Specification.new do |s|
# s.add_development_dependency "rcov"
s.add_development_dependency "rspec"
s.add_development_dependency "ruby-saml"
s.add_development_dependency "actionpack"
end

56 changes: 43 additions & 13 deletions spec/controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,50 @@ def params
saml_acs_url.should == requested_saml_acs_url
end

it "should create a SAML Response" do
requested_saml_acs_url = "https://foo.example.com/saml/consume"
saml_config = saml_settings(requested_saml_acs_url)
auth_request = Onelogin::Saml::Authrequest.new
auth_url = auth_request.create(saml_config)
params[:SAMLRequest] = CGI.unescape(auth_url.split("=").last)
validate_saml_request
saml_response = encode_SAMLResponse("[email protected]")
context "SAML Responses" do
before(:each) do
requested_saml_acs_url = "https://foo.example.com/saml/consume"
@saml_config = saml_settings(requested_saml_acs_url)
auth_request = Onelogin::Saml::Authrequest.new
auth_url = auth_request.create(@saml_config)
params[:SAMLRequest] = CGI.unescape(auth_url.split("=").last)
validate_saml_request
end

response = Onelogin::Saml::Response.new(saml_response)
response.name_id.should == "[email protected]"
response.issuer.should == "http://example.com"
response.settings = saml_config
response.is_valid?.should be_true
it "should create a SAML Response" do
saml_response = encode_SAMLResponse("[email protected]")
response = Onelogin::Saml::Response.new(saml_response)
response.name_id.should == "[email protected]"
response.issuer.should == "http://example.com"
response.settings = @saml_config
response.is_valid?.should be_true
end

[:sha1, :sha256].each do |algorithm_name|
it "should create a SAML Response using the #{algorithm_name} algorithm" do
self.algorithm = algorithm_name
saml_response = encode_SAMLResponse("[email protected]")
response = Onelogin::Saml::Response.new(saml_response)
response.name_id.should == "[email protected]"
response.issuer.should == "http://example.com"
response.settings = @saml_config
response.is_valid?.should be_true
end
end

[:sha384, :sha512].each do |algorithm_name|
it "should create a SAML Response using the #{algorithm_name} algorithm" do
pending "release of ruby-saml v0.5.4" do
self.algorithm = algorithm_name
saml_response = encode_SAMLResponse("[email protected]")
response = Onelogin::Saml::Response.new(saml_response)
response.name_id.should == "[email protected]"
response.issuer.should == "http://example.com"
response.settings = @saml_config
response.is_valid?.should be_true
end
end
end
end

private
Expand Down

0 comments on commit a98e44b

Please sign in to comment.