Skip to content

Commit 80ebeb1

Browse files
author
John Lynch
committed
Implement signed AuthnRequests
1 parent e6ff196 commit 80ebeb1

File tree

5 files changed

+66
-7
lines changed

5 files changed

+66
-7
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,27 @@ redirect_to saml_request.url(
2222
)
2323
```
2424

25+
### Initiating a signed authentication request
26+
27+
The signed SAML request must have a Destination node, specified as `destination_url`
28+
29+
```ruby
30+
saml_request = Samlr::Request.new(
31+
:issuer => request.host,
32+
:name_identity_format => Samlr::EMAIL_FORMAT,
33+
:consumer_service_url => "https://#{request.host}/auth/saml",
34+
:sign_requests => true,
35+
:destination_url => "https://adfs.company.com/adfs/ls/",
36+
:certificate => Samlr::Tools::CertificateBuilder.read(private_key_pem, certifictate_pem)
37+
)
38+
```
39+
This will sign the URL with your private key and look something like this:
40+
41+
```
42+
https://foo.com/?SAMLRequest=...Base64 Encoded SAML Request...&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=tvY57vi6IXHP1gHAMRQoRP5CZQlUniPwSeuwOUypqbjim04svTkk72njvbxzUE27U5PhK0Cwzq4ZdZ08i%2BuVAw%3D%3D
43+
```
44+
45+
2546
Once the IdP receives the request, it prompts the user to authenticate, after which it sends the SAML response to your application.
2647

2748
### Verifying a SAML response

lib/samlr/request.rb

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,35 @@ def body
2020

2121
# Utility method to get the full redirect destination, Request#url("https://idp.example.com/saml", { :RelayState => "https://sp.example.com/saml" })
2222
def url(root, params = {})
23-
dest = root.dup
24-
if dest.include?("?")
25-
dest << "&SAMLRequest=#{param}"
26-
else
27-
dest << "?SAMLRequest=#{param}"
23+
params = params.dup
24+
buffer = root.dup
25+
buffer << (buffer.include?("?") ? "&" : "?")
26+
27+
signable = "SAMLRequest=#{param}"
28+
29+
if params[:RelayState]
30+
signable << "&RelayState=#{CGI.escape(params.delete(:RelayState))}"
31+
end
32+
33+
if options[:sign_requests]
34+
signable << "&SigAlg=#{CGI.escape('http://www.w3.org/2000/09/xmldsig#rsa-sha1')}"
35+
signable << "&Signature=#{CGI.escape(compute_signature(signable))}"
2836
end
2937

38+
buffer << signable
39+
3040
params.each_pair do |key, value|
31-
dest << "&#{key}=#{CGI.escape(value.to_s)}"
41+
buffer << "&#{key}=#{CGI.escape(value.to_s)}"
3242
end
3343

34-
dest
44+
buffer
45+
end
46+
47+
private
48+
def compute_signature(signable)
49+
certificate = options[:certificate] #instance of Samlr::Tools::CertificateBuilder
50+
certificate.sign(signable)
3551
end
52+
3653
end
3754
end

lib/samlr/tools/certificate_builder.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ def self.load(path, id = "samlr")
6969

7070
new(:key_pair => key_pair, :x509 => x509_cert)
7171
end
72+
73+
def self.read(private_key_pem, certificate_pem)
74+
key_pair = OpenSSL::PKey::RSA.new(private_key_pem)
75+
x509_cert = OpenSSL::X509::Certificate.new(certificate_pem)
76+
77+
new(:key_pair => key_pair, :x509 => x509_cert)
78+
end
7279
end
7380
end
7481
end

lib/samlr/tools/request_builder.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Tools
77
module RequestBuilder
88
def self.build(options = {})
99
consumer_service_url = options[:consumer_service_url]
10+
destination_url = options[:destination_url]
1011
issuer = options[:issuer]
1112
name_identity_format = options[:name_identity_format]
1213
allow_create = options[:allow_create] || "true"
@@ -20,6 +21,10 @@ def self.build(options = {})
2021
xml.doc.root["AssertionConsumerServiceURL"] = consumer_service_url
2122
end
2223

24+
unless destination_url.nil?
25+
xml.doc.root["Destination"] = destination_url
26+
end
27+
2328
unless issuer.nil?
2429
xml["saml"].Issuer(issuer)
2530
end

test/unit/test_request.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,13 @@
3131
end
3232
end
3333
end
34+
35+
describe "#signed_url" do
36+
let(:signed_request) {Samlr::Request.new(:sign_requests => true, :certificate => TEST_CERTIFICATE)}
37+
it "returns a signed URL" do
38+
signed_request.stub(:param, "hello") do
39+
assert_equal("https://foo.com/?SAMLRequest=hello&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=tvY57vi6IXHP1gHAMRQoRP5CZQlUniPwSeuwOUypqbjim04svTkk72njvbxzUE27U5PhK0Cwzq4ZdZ08i%2BuVAw%3D%3D&foo=bar", signed_request.url("https://foo.com/", :foo => "bar"))
40+
end
41+
end
42+
end
3443
end

0 commit comments

Comments
 (0)