samlr 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of samlr might be problematic. Click here for more details.

Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +176 -0
  6. data/README.md +182 -0
  7. data/Rakefile +12 -0
  8. data/bin/samlr +46 -0
  9. data/config/schemas/XMLSchema.xsd +2534 -0
  10. data/config/schemas/saml-schema-assertion-2.0.xsd +283 -0
  11. data/config/schemas/saml-schema-metadata-2.0.xsd +337 -0
  12. data/config/schemas/saml-schema-protocol-2.0.xsd +302 -0
  13. data/config/schemas/xenc-schema.xsd +146 -0
  14. data/config/schemas/xml.xsd +287 -0
  15. data/config/schemas/xmldsig-core-schema.xsd +318 -0
  16. data/lib/samlr.rb +52 -0
  17. data/lib/samlr/assertion.rb +91 -0
  18. data/lib/samlr/certificate.rb +23 -0
  19. data/lib/samlr/command.rb +41 -0
  20. data/lib/samlr/condition.rb +31 -0
  21. data/lib/samlr/errors.rb +22 -0
  22. data/lib/samlr/fingerprint.rb +44 -0
  23. data/lib/samlr/logout_request.rb +7 -0
  24. data/lib/samlr/reference.rb +32 -0
  25. data/lib/samlr/request.rb +37 -0
  26. data/lib/samlr/response.rb +68 -0
  27. data/lib/samlr/signature.rb +129 -0
  28. data/lib/samlr/tools.rb +108 -0
  29. data/lib/samlr/tools/certificate_builder.rb +74 -0
  30. data/lib/samlr/tools/logout_request_builder.rb +27 -0
  31. data/lib/samlr/tools/metadata_builder.rb +41 -0
  32. data/lib/samlr/tools/request_builder.rb +44 -0
  33. data/lib/samlr/tools/response_builder.rb +157 -0
  34. data/lib/samlr/tools/timestamp.rb +26 -0
  35. data/samlr.gemspec +19 -0
  36. data/test/fixtures/default_samlr_certificate.pem +11 -0
  37. data/test/fixtures/default_samlr_private_key.pem +9 -0
  38. data/test/fixtures/no_cert_response.xml +2 -0
  39. data/test/fixtures/sample_metadata.xml +7 -0
  40. data/test/fixtures/sample_response.xml +2 -0
  41. data/test/test_helper.rb +55 -0
  42. data/test/unit/test_assertion.rb +54 -0
  43. data/test/unit/test_condition.rb +71 -0
  44. data/test/unit/test_fingerprint.rb +45 -0
  45. data/test/unit/test_logout_request.rb +39 -0
  46. data/test/unit/test_reference.rb +32 -0
  47. data/test/unit/test_request.rb +34 -0
  48. data/test/unit/test_response.rb +94 -0
  49. data/test/unit/test_response_scenarios.rb +111 -0
  50. data/test/unit/test_signature.rb +54 -0
  51. data/test/unit/test_timestamp.rb +58 -0
  52. data/test/unit/test_tools.rb +100 -0
  53. data/test/unit/tools/test_certificate_builder.rb +41 -0
  54. data/test/unit/tools/test_logout_request_builder.rb +26 -0
  55. data/test/unit/tools/test_metadata_builder.rb +26 -0
  56. data/test/unit/tools/test_request_builder.rb +35 -0
  57. data/test/unit/tools/test_response_builder.rb +19 -0
  58. metadata +184 -0
@@ -0,0 +1,111 @@
1
+ require File.expand_path("test/test_helper")
2
+
3
+ # The tests in here are integraton level tests. They pass various mutations of a response
4
+ # document to the stack and asserts behavior.
5
+ describe Samlr do
6
+
7
+ describe "a valid response" do
8
+ subject { saml_response(:certificate => TEST_CERTIFICATE) }
9
+
10
+ it "verifies" do
11
+ assert subject.verify!
12
+ assert_equal "someone@example.org", subject.name_id
13
+ end
14
+ end
15
+
16
+ describe "an invalid fingerprint" do
17
+ subject { saml_response(:certificate => TEST_CERTIFICATE, :fingerprint => "hello") }
18
+ it "fails" do
19
+ assert_raises(Samlr::FingerprintError) { subject.verify! }
20
+ end
21
+ end
22
+
23
+ describe "an unsatisfied before condition" do
24
+ subject { saml_response(:certificate => TEST_CERTIFICATE, :not_before => Samlr::Tools::Timestamp.stamp(Time.now + 60)) }
25
+
26
+ it "fails" do
27
+ assert_raises(Samlr::ConditionsError) { subject.verify! }
28
+ end
29
+
30
+ describe "when jitter is in effect" do
31
+ after { Samlr.jitter = nil }
32
+
33
+ it "passes" do
34
+ Samlr.jitter = 500
35
+ assert subject.verify!
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "an unsatisfied after condition" do
41
+ subject { saml_response(:certificate => TEST_CERTIFICATE, :not_on_or_after => Samlr::Tools::Timestamp.stamp(Time.now - 60)) }
42
+
43
+ it "fails" do
44
+ assert_raises(Samlr::ConditionsError) { subject.verify! }
45
+ end
46
+
47
+ describe "when jitter is in effect" do
48
+ after { Samlr.jitter = nil }
49
+
50
+ it "passes" do
51
+ Samlr.jitter = 500
52
+ assert subject.verify!
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "when there are no attributes" do
58
+ subject { saml_response(:certificate => TEST_CERTIFICATE, :attributes => {}) }
59
+
60
+ it "returns an empty hash" do
61
+ assert_equal({}, subject.attributes)
62
+ end
63
+ end
64
+
65
+ describe "when there are no signatures" do
66
+ subject { saml_response(:certificate => TEST_CERTIFICATE, :sign_assertion => false, :sign_response => false) }
67
+
68
+ it "fails" do
69
+ assert_raises(Samlr::SignatureError) { subject.verify! }
70
+ end
71
+ end
72
+
73
+ describe "when there is no keyinfo" do
74
+ subject { saml_response(:certificate => TEST_CERTIFICATE, :skip_keyinfo => true) }
75
+
76
+ it "fails" do
77
+ assert_raises(Samlr::SignatureError) { subject.verify! }
78
+ end
79
+
80
+ describe "when a matching external cert is provided" do
81
+ it "passes" do
82
+ subject.options[:certificate] = TEST_CERTIFICATE.x509
83
+ assert subject.verify!
84
+ end
85
+ end
86
+
87
+ describe "when a non-matching external cert is provided" do
88
+ it "fails" do
89
+ subject.options[:certificate] = Samlr::Tools::CertificateBuilder.new.x509
90
+ assert_raises(Samlr::FingerprintError) { subject.verify! }
91
+ end
92
+ end
93
+ end
94
+
95
+ describe "when there's no assertion" do
96
+ subject { saml_response(:certificate => TEST_CERTIFICATE, :sign_assertion => false, :skip_assertion => true) }
97
+
98
+ it "fails" do
99
+ assert_raises(Samlr::FormatError) { subject.verify! }
100
+ end
101
+ end
102
+
103
+ describe "duplicate element ids" do
104
+ subject { saml_response(:certificate => TEST_CERTIFICATE, :response_id => "abcdef", :assertion_id => "abcdef") }
105
+
106
+ it "fails" do
107
+ assert_raises(Samlr::FormatError) { subject.verify! }
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,54 @@
1
+ require File.expand_path("test/test_helper")
2
+ require "openssl"
3
+
4
+ describe Samlr::Signature do
5
+ before do
6
+ @response = fixed_saml_response
7
+ @signature = @response.signature
8
+ end
9
+
10
+ describe "#signature_algorithm" do
11
+ it "should defer to Samlr::Tools::algorithm" do
12
+ Samlr::Tools.stub(:algorithm, "hello") do
13
+ assert_match "hello", @signature.send(:signature_method)
14
+ end
15
+ end
16
+ end
17
+
18
+ describe "#references" do
19
+ it "should extract the reference to the signed document" do
20
+ assert_equal @response.document.children.first, @response.document.at(".//*[@ID='#{@signature.send(:references).first.uri}']")
21
+ end
22
+ end
23
+
24
+ describe "#certificate" do
25
+ it "should extract the certificate" do
26
+ assert_equal TEST_CERTIFICATE.to_certificate, @signature.send(:certificate)
27
+ end
28
+
29
+ describe "when there is no X509 certificate" do
30
+ it "should raise a signature error" do
31
+ @signature.stub(:certificate_node, nil) do
32
+ assert_raises(Samlr::SignatureError) { @signature.send(:certificate) }
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "#verify_digests!" do
39
+ describe "when there are duplicate element ids" do
40
+ before do
41
+ @signature.document.at("/samlp:Response/saml:Assertion")["ID"] = @signature.document.root["ID"]
42
+ end
43
+
44
+ it "should raise" do
45
+ begin
46
+ @signature.send(:verify_digests!)
47
+ flunk("Excepted to raise due to duplicate elements")
48
+ rescue Samlr::SignatureError => e
49
+ assert_equal "Reference validation error: Invalid element references", e.message
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path("test/test_helper")
2
+
3
+ describe Samlr::Tools::Timestamp do
4
+ before { Samlr.jitter = nil }
5
+ after { Samlr.jitter = nil }
6
+
7
+ describe "::parse" do
8
+ before { @time = ::Time.now }
9
+ it "turns an iso8601 string into a time instance" do
10
+ iso8601 = @time.utc.iso8601
11
+ assert_equal @time.to_i, Samlr::Tools::Timestamp.parse(iso8601).to_i
12
+ end
13
+ end
14
+
15
+ describe "::stamp" do
16
+ it "converts a given time to an iso8601 string in UTC" do
17
+ assert_equal "2012-08-08T18:28:38Z", Samlr::Tools::Timestamp.stamp(Time.at(1344450518))
18
+ end
19
+
20
+ it "defaults to a current timestamp in iso8601" do
21
+ assert ::Time.iso8601(Samlr::Tools::Timestamp.stamp).is_a?(Time)
22
+ end
23
+ end
24
+
25
+ describe "::not_on_or_after?" do
26
+ describe "when no jitter is allowed" do
27
+ it "disallows imprecision" do
28
+ assert Samlr::Tools::Timestamp.not_on_or_after?(Time.now + 5)
29
+ end
30
+ end
31
+
32
+ describe "when jitter is allowed" do
33
+ before { Samlr.jitter = 10 }
34
+
35
+ it "allows imprecision" do
36
+ assert Samlr::Tools::Timestamp.not_on_or_after?(Time.now - 5)
37
+ refute Samlr::Tools::Timestamp.not_on_or_after?(Time.now - 15)
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "::before?" do
43
+ describe "when no jitter is allowed" do
44
+ it "disallows imprecision" do
45
+ assert Samlr::Tools::Timestamp.not_before?(Time.now - 5)
46
+ end
47
+ end
48
+
49
+ describe "when jitter is allowed" do
50
+ before { Samlr.jitter = 10 }
51
+
52
+ it "allows imprecision" do
53
+ assert Samlr::Tools::Timestamp.not_before?(Time.now + 5)
54
+ refute Samlr::Tools::Timestamp.not_before?(Time.now + 15)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,100 @@
1
+ require File.expand_path("test/test_helper")
2
+ require "openssl"
3
+
4
+ describe Samlr::Tools do
5
+
6
+ describe "::canonicalize" do
7
+ before do
8
+ @fixture = fixed_saml_response.document.to_xml
9
+ end
10
+
11
+ it "should namespace the SignedInfo element" do
12
+ path = "/samlp:Response/ds:Signature/ds:SignedInfo"
13
+ assert_match '<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">', Samlr::Tools.canonicalize(@fixture, { :path => path })
14
+ end
15
+ end
16
+
17
+ describe "::uuid" do
18
+ it "generates a valid xs:ID" do
19
+ assert Samlr::Tools.uuid !~ /^\d/
20
+ end
21
+ end
22
+
23
+ describe "::algorithm" do
24
+ [ 1, 384, 512 ].each do |i|
25
+ describe "when fed SHA#{i}" do
26
+ subject { "#sha#{i}" }
27
+
28
+ it "should return the corresponding implementation" do
29
+ assert_equal eval("OpenSSL::Digest::SHA#{i}"), Samlr::Tools.algorithm(subject)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "when not specified" do
35
+ subject { nil }
36
+
37
+ it "should default to SHA1" do
38
+ assert_equal OpenSSL::Digest::SHA1, Samlr::Tools.algorithm(subject)
39
+ end
40
+ end
41
+
42
+ describe "when not known" do
43
+ subject { "sha73" }
44
+
45
+ it "should default to SHA1" do
46
+ assert_equal OpenSSL::Digest::SHA1, Samlr::Tools.algorithm(subject)
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "::encode and ::decode" do
52
+ it "compresses a string in a reversible fashion" do
53
+ assert_equal "12345678", Samlr::Tools.decode(Samlr::Tools.encode("12345678"))
54
+ end
55
+ end
56
+
57
+ describe "::validate" do
58
+ subject { saml_response_document(:certificate => TEST_CERTIFICATE) }
59
+
60
+ it "returns true for valid documents" do
61
+ assert Samlr::Tools.validate(:document => subject)
62
+ end
63
+
64
+ it "returns false for invalid documents" do
65
+ mangled = subject.gsub("Assertion", "AyCaramba")
66
+ refute Samlr::Tools.validate(:document => mangled)
67
+ end
68
+
69
+ it "does not change the working directory" do
70
+ path = Dir.pwd
71
+ assert Samlr::Tools.validate(:document => subject)
72
+ assert_equal path, Dir.pwd
73
+ end
74
+ end
75
+
76
+ describe "::validate!" do
77
+ subject { saml_response_document(:certificate => TEST_CERTIFICATE) }
78
+
79
+ it "returns true for valid documents" do
80
+ assert Samlr::Tools.validate!(:document => subject)
81
+ end
82
+
83
+ it "raises for invalid documents" do
84
+ mangled = subject.gsub("Assertion", "AyCaramba")
85
+
86
+ begin
87
+ Samlr::Tools.validate!(:document => mangled)
88
+ flunk "Errors expected"
89
+ rescue Samlr::FormatError => e
90
+ assert_equal "Schema validation failed", e.message
91
+ end
92
+ end
93
+
94
+ it "does not change the working directory" do
95
+ path = Dir.pwd
96
+ assert Samlr::Tools.validate!(:document => subject)
97
+ assert_equal path, Dir.pwd
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path("test/test_helper")
2
+
3
+ describe Samlr::Tools::CertificateBuilder do
4
+ before { @certificate = TEST_CERTIFICATE }
5
+
6
+ it "provides a certificate" do
7
+ assert_equal OpenSSL::X509::Certificate, @certificate.x509.class
8
+ end
9
+
10
+ describe "#verify" do
11
+ it "verifies its own signature" do
12
+ assert @certificate.verify(@certificate.sign("12345678"), "12345678")
13
+ end
14
+ end
15
+
16
+ describe "serialization" do
17
+ before do
18
+ @path = Dir.tmpdir
19
+ Dir.glob("#{@path}/*.pem").map { |f| File.unlink(f) }
20
+ end
21
+
22
+ describe "self#dump" do
23
+ before { Samlr::Tools::CertificateBuilder.dump(@path, @certificate) }
24
+
25
+ it "creates a key file and a certificate file on disk" do
26
+ state = Dir.glob("#{@path}/*.pem")
27
+ assert_equal 2, state.size
28
+ end
29
+
30
+ describe "#load" do
31
+ before { @loaded = Samlr::Tools::CertificateBuilder.load(@path) }
32
+
33
+ it "verified the signature signed by the unserialized certificate" do
34
+ assert @loaded.verify(@certificate.sign("12345678"), "12345678")
35
+ assert @certificate.verify(@loaded.sign("12345678"), "12345678")
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path("test/test_helper")
2
+
3
+ describe Samlr::Tools::LogoutRequestBuilder do
4
+ describe "#build" do
5
+ before do
6
+ @xml = Samlr::Tools::LogoutRequestBuilder.build(
7
+ :issuer => "https://sp.example.com/saml2",
8
+ :name_id => "test@test.com"
9
+ )
10
+
11
+ @doc = Nokogiri::XML(@xml) { |c| c.strict }
12
+ end
13
+
14
+ it "generates a request document" do
15
+ assert_equal "LogoutRequest", @doc.root.name
16
+
17
+ issuer = @doc.root.at("./saml:Issuer", Samlr::NS_MAP)
18
+ assert_equal "https://sp.example.com/saml2", issuer.text
19
+ end
20
+
21
+ it "validates against schemas" do
22
+ result = Samlr::Tools.validate(:document => @xml)
23
+ assert result
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path("test/test_helper")
2
+
3
+ describe Samlr::Tools::MetadataBuilder do
4
+ describe "#build" do
5
+ before do
6
+ @xml = Samlr::Tools::MetadataBuilder.build({
7
+ :entity_id => "https://sp.example.com/saml2",
8
+ :name_identity_format => "identity_format",
9
+ :consumer_service_url => "https://support.sp.example.com/"
10
+ })
11
+
12
+ @doc = Nokogiri::XML(@xml) { |c| c.strict }
13
+ end
14
+
15
+ it "generates a metadata document" do
16
+ assert_equal "EntityDescriptor", @doc.root.name
17
+ assert_equal "identity_format", @doc.at("//md:NameIDFormat", { "md" => Samlr::NS_MAP["md"] }).text
18
+ end
19
+
20
+ it "validates against schemas" do
21
+ result = Samlr::Tools.validate(:document => @xml, :schema => Samlr::META_SCHEMA)
22
+ assert result
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ require File.expand_path("test/test_helper")
2
+
3
+ describe Samlr::Tools::RequestBuilder do
4
+ describe "#build" do
5
+ before do
6
+ @xml = Samlr::Tools::RequestBuilder.build({
7
+ :issuer => "https://sp.example.com/saml2",
8
+ :name_identity_format => "identity_format",
9
+ :allow_create => "true",
10
+ :consumer_service_url => "https://support.sp.example.com/",
11
+ :authn_context => "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
12
+ })
13
+
14
+ @doc = Nokogiri::XML(@xml) { |c| c.strict }
15
+ end
16
+
17
+ it "generates a request document" do
18
+ assert_equal "AuthnRequest", @doc.root.name
19
+ assert_equal "https://support.sp.example.com/", @doc.root["AssertionConsumerServiceURL"]
20
+
21
+ issuer = @doc.root.at("./saml:Issuer", Samlr::NS_MAP)
22
+ assert_equal "https://sp.example.com/saml2", issuer.text
23
+
24
+ name_id_policy = @doc.root.at("./samlp:NameIDPolicy", Samlr::NS_MAP)
25
+ assert_equal "true", name_id_policy["AllowCreate"]
26
+ assert_equal "identity_format", name_id_policy["Format"]
27
+ end
28
+
29
+ it "validates against schemas" do
30
+ result = Samlr::Tools.validate(:document => @xml)
31
+ assert result
32
+ end
33
+
34
+ end
35
+ end