omniauth-islykill 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/README.md +103 -0
- data/lib/omniauth-islykill.rb +3 -0
- data/lib/omniauth-islykill/version.rb +5 -0
- data/lib/omniauth/strategies/islykill.rb +103 -0
- data/lib/omniauth/strategies/islykill/validation_error.rb +8 -0
- data/lib/signed_xml.rb +40 -0
- data/lib/signed_xml/base64_transform.rb +9 -0
- data/lib/signed_xml/c14n_transform.rb +26 -0
- data/lib/signed_xml/digest_method_resolution.rb +19 -0
- data/lib/signed_xml/digest_transform.rb +17 -0
- data/lib/signed_xml/document.rb +70 -0
- data/lib/signed_xml/enveloped_signature_transform.rb +10 -0
- data/lib/signed_xml/fingerprinting.rb +9 -0
- data/lib/signed_xml/logging.rb +13 -0
- data/lib/signed_xml/reference.rb +73 -0
- data/lib/signed_xml/signature.rb +137 -0
- data/lib/signed_xml/signed_info.rb +17 -0
- data/lib/signed_xml/transformable.rb +20 -0
- data/lib/signed_xml/version.rb +3 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 56fced1e74fbd3dfc280e4bcf348fe2d9d618108
|
4
|
+
data.tar.gz: 5b4c83f5ba4f4d904254024305263cf5643f6368
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0aba1334e79e01ee4446173e95029afc09f0cfe55ae40b1fec260edf069d2c75ccf4dd305c8ada165bec73681aa1d1ae0eeea9d4ff84fc8940a2a64864f801aa
|
7
|
+
data.tar.gz: fdfc5a2a975b9d89e0ed82c9fc1dc74c35ef1ad3d211d67dfd716319625afac14ca5fbba8530b2be0a0beb47903577d75ff962b0681d90d46b894e8aaed20f9e
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# OmniAuth Islykill
|
2
|
+
|
3
|
+
This is a specific SAML strategy that handles authentication to Icelands Íslykill for OmniAuth
|
4
|
+
|
5
|
+
https://github.com/Algrim/omniauth-islykill
|
6
|
+
|
7
|
+
## Requirements
|
8
|
+
|
9
|
+
* [OmniAuth](http://www.omniauth.org/) 1.2+
|
10
|
+
* [ruby-saml](https://github.com/onelogin/ruby-saml)
|
11
|
+
* Ruby 1.9.x or Ruby 2.1.x
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Use the Islykill strategy in your Rails application:
|
16
|
+
|
17
|
+
in `Gemfile`:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'omniauth-islykill'
|
21
|
+
```
|
22
|
+
|
23
|
+
and in `config/initializers/omniauth.rb`:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
27
|
+
provider :islykill,
|
28
|
+
:assertion_consumer_service_url => "consumer_service_url",
|
29
|
+
:issuer => "Islyklar",
|
30
|
+
:idp_sso_target_url => "https://innskraning.island.is/?id=consumer_service_url",
|
31
|
+
:idp_cert => "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----",
|
32
|
+
:idp_cert_fingerprint => "E7:91:B2:E1:...",
|
33
|
+
:name_identifier_format => "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
For IdP-initiated SSO, users should directly access the IdP SSO target URL. Set the `href` of your application's login link to the value of `idp_sso_target_url`. For SP-initiated SSO, link to `/auth/saml`.
|
38
|
+
|
39
|
+
## Metadata
|
40
|
+
|
41
|
+
The service provider metadata used to ease configuration of the SAML SP in the IdP can be retrieved from `http://example.com/auth/saml/metadata`. Send this URL to the administrator of the IdP.
|
42
|
+
|
43
|
+
## Options
|
44
|
+
|
45
|
+
* `:assertion_consumer_service_url` - The URL at which the SAML assertion should be
|
46
|
+
received. If not provided, defaults to the OmniAuth callback URL (typically
|
47
|
+
`http://example.com/auth/saml/callback`). Optional.
|
48
|
+
|
49
|
+
* `:issuer` - The name of your application. Some identity providers might need this
|
50
|
+
to establish the identity of the service provider requesting the login. **Required**.
|
51
|
+
|
52
|
+
* `:idp_sso_target_url` - The URL to which the authentication request should be sent.
|
53
|
+
This would be on the identity provider. **Required**.
|
54
|
+
|
55
|
+
* `:idp_sso_target_url_runtime_params` - A dynamic mapping of request params that exist
|
56
|
+
during the request phase of OmniAuth that should to be sent to the IdP after a specific
|
57
|
+
mapping. So for example, a param `original_request_param` with value `original_param_value`,
|
58
|
+
could be sent to the IdP on the login request as `mapped_idp_param` with value
|
59
|
+
`original_param_value`. Optional.
|
60
|
+
|
61
|
+
* `:idp_cert` - The identity provider's certificate in PEM format. Takes precedence
|
62
|
+
over the fingerprint option below. This option or `:idp_cert_fingerprint` must
|
63
|
+
be present.
|
64
|
+
|
65
|
+
* `:idp_cert_fingerprint` - The SHA1 fingerprint of the certificate, e.g.
|
66
|
+
"90:CC:16:F0:8D:...". This is provided from the identity provider when setting up
|
67
|
+
the relationship. This option or `:idp_cert` must be present.
|
68
|
+
|
69
|
+
* `:name_identifier_format` - Used during SP-initiated SSO. Describes the format of
|
70
|
+
the username required by this application. If you need the email address, use
|
71
|
+
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress". See
|
72
|
+
http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf section 8.3 for
|
73
|
+
other options. Note that the identity provider might not support all options.
|
74
|
+
If not specified, the IdP is free to choose the name identifier format used
|
75
|
+
in the response. Optional.
|
76
|
+
|
77
|
+
* See the `Onelogin::Saml::Settings` class in the [Ruby SAML gem](https://github.com/onelogin/ruby-saml) for additional supported options.
|
78
|
+
|
79
|
+
## Authors
|
80
|
+
|
81
|
+
Authored by Björgvin Bæhrenz Þórðarson.
|
82
|
+
|
83
|
+
Maintained by [Björgvin Bæhrenz Þórðarson](https://github.com/Bjorgvin).
|
84
|
+
|
85
|
+
## License
|
86
|
+
|
87
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
88
|
+
of this software and associated documentation files (the "Software"), to deal
|
89
|
+
in the Software without restriction, including without limitation the rights
|
90
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
91
|
+
copies of the Software, and to permit persons to whom the Software is
|
92
|
+
furnished to do so, subject to the following conditions:
|
93
|
+
|
94
|
+
The above copyright notice and this permission notice shall be included in
|
95
|
+
all copies or substantial portions of the Software.
|
96
|
+
|
97
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
98
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
99
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
100
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
101
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
102
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
103
|
+
THE SOFTWARE.
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'omniauth'
|
2
|
+
require 'ruby-saml'
|
3
|
+
|
4
|
+
module OmniAuth
|
5
|
+
module Strategies
|
6
|
+
class Islykill
|
7
|
+
include OmniAuth::Strategy
|
8
|
+
|
9
|
+
option :name_identifier_format, nil
|
10
|
+
option :idp_sso_target_url_runtime_params, {}
|
11
|
+
|
12
|
+
def request_phase
|
13
|
+
options[:assertion_consumer_service_url] ||= callback_url
|
14
|
+
runtime_request_parameters = options.delete(:idp_sso_target_url_runtime_params)
|
15
|
+
|
16
|
+
additional_params = {}
|
17
|
+
runtime_request_parameters.each_pair do |request_param_key, mapped_param_key|
|
18
|
+
additional_params[mapped_param_key] = request.params[request_param_key.to_s] if request.params.has_key?(request_param_key.to_s)
|
19
|
+
end if runtime_request_parameters
|
20
|
+
|
21
|
+
authn_request = Onelogin::Saml::Authrequest.new
|
22
|
+
settings = Onelogin::Saml::Settings.new(options)
|
23
|
+
|
24
|
+
redirect(authn_request.create(settings, additional_params))
|
25
|
+
end
|
26
|
+
|
27
|
+
def callback_phase
|
28
|
+
puts " ___ _ _ _ _ "
|
29
|
+
puts " / __ __ _| | | |__ __ _ ___| | __"
|
30
|
+
puts " / / / _` | | | '_ / _` |/ __| |/ /"
|
31
|
+
puts "/ /__| (_| | | | |_) | (_| | (__| < "
|
32
|
+
puts " ____/ __,_|_|_|_.__/ __,_| ___|_| _ "
|
33
|
+
puts " "
|
34
|
+
|
35
|
+
unless request.params['token']
|
36
|
+
raise OmniAuth::Strategies::Islykill::ValidationError.new("Islykill response missing")
|
37
|
+
end
|
38
|
+
|
39
|
+
token_base64 = request.params['token']
|
40
|
+
islykill_xml_saml_response = Base64.decode64(token_base64)
|
41
|
+
signedDocument = SignedXml::Document(islykill_xml_saml_response)
|
42
|
+
if !signedDocument.is_verified?
|
43
|
+
raise OmniAuth::Strategies::Islykill::ValidationError.new("Islykill response not valid")
|
44
|
+
end
|
45
|
+
|
46
|
+
# response is valid so we extract the information using xpath
|
47
|
+
xml_doc = REXML::Document.new(islykill_xml_saml_response)
|
48
|
+
prefix='Response/Assertion/AttributeStatement/Attribute[@Name="'
|
49
|
+
postfix='"]/AttributeValue'
|
50
|
+
|
51
|
+
@attributes={
|
52
|
+
name: REXML::XPath.first(xml_doc,"#{prefix}Name#{postfix}").text,
|
53
|
+
kennitala: REXML::XPath.first(xml_doc,"#{prefix}UserSSN#{postfix}").text,
|
54
|
+
provider: REXML::XPath.first(xml_doc,"#{prefix}Authentication#{postfix}").text
|
55
|
+
}
|
56
|
+
|
57
|
+
@name_id = REXML::XPath.first(xml_doc,"Response/Assertion/Subject/NameID/@NameQualifier").value()
|
58
|
+
|
59
|
+
if @name_id.nil? || @name_id.empty?
|
60
|
+
raise OmniAuth::Strategies::Islykill::ValidationError.new("SAML response missing 'name_id'")
|
61
|
+
end
|
62
|
+
|
63
|
+
super
|
64
|
+
rescue
|
65
|
+
fail!(:invalid_ticket, $!)
|
66
|
+
rescue Onelogin::Saml::ValidationError
|
67
|
+
fail!(:invalid_ticket, $!)
|
68
|
+
end
|
69
|
+
|
70
|
+
# def other_phase
|
71
|
+
# if on_path?("#{request_path}/metadata")
|
72
|
+
# # omniauth does not set the strategy on the other_phase
|
73
|
+
# @env['omniauth.strategy'] ||= self
|
74
|
+
# setup_phase
|
75
|
+
|
76
|
+
# response = Onelogin::Saml::Metadata.new
|
77
|
+
# settings = Onelogin::Saml::Settings.new(options)
|
78
|
+
# Rack::Response.new(response.generate(settings), 200, { "Content-Type" => "application/xml" }).finish
|
79
|
+
# else
|
80
|
+
# call_app!
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
|
84
|
+
uid {
|
85
|
+
#@name_id
|
86
|
+
@attributes[:kennitala]
|
87
|
+
}
|
88
|
+
|
89
|
+
info do
|
90
|
+
{
|
91
|
+
:name => @attributes[:name],
|
92
|
+
:kennitala => @attributes[:kennitala]
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
extra { { :raw_info => @attributes } }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
|
data/lib/signed_xml.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module SignedXml
|
5
|
+
XMLDSIG_NS = "http://www.w3.org/2000/09/xmldsig#"
|
6
|
+
|
7
|
+
def self.Document(thing)
|
8
|
+
Document.new(thing)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Logger that does nothing
|
12
|
+
BITBUCKET_LOGGER = Logger.new(nil)
|
13
|
+
class << BITBUCKET_LOGGER
|
14
|
+
def add(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# The logger signed_xml should use
|
19
|
+
def self.logger
|
20
|
+
@@logger ||= BITBUCKET_LOGGER
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set the logger for signed_xml
|
24
|
+
def self.logger=(a_logger)
|
25
|
+
@@logger = a_logger
|
26
|
+
end
|
27
|
+
|
28
|
+
autoload :Transformable, 'signed_xml/transformable'
|
29
|
+
autoload :Document, 'signed_xml/document'
|
30
|
+
autoload :Signature, 'signed_xml/signature'
|
31
|
+
autoload :SignedInfo, 'signed_xml/signed_info'
|
32
|
+
autoload :Reference, 'signed_xml/reference'
|
33
|
+
autoload :DigestMethodResolution, 'signed_xml/digest_method_resolution'
|
34
|
+
autoload :DigestTransform, 'signed_xml/digest_transform'
|
35
|
+
autoload :Base64Transform, 'signed_xml/base64_transform'
|
36
|
+
autoload :C14NTransform, 'signed_xml/c14n_transform'
|
37
|
+
autoload :EnvelopedSignatureTransform, 'signed_xml/enveloped_signature_transform'
|
38
|
+
autoload :Fingerprinting, 'signed_xml/fingerprinting'
|
39
|
+
autoload :Logging, 'signed_xml/logging'
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SignedXml
|
2
|
+
class C14NTransform
|
3
|
+
include Nokogiri::XML
|
4
|
+
|
5
|
+
attr_reader :method
|
6
|
+
attr_reader :with_comments
|
7
|
+
|
8
|
+
def initialize(method = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315")
|
9
|
+
method, with_comments = method.split('#')
|
10
|
+
@method = case method
|
11
|
+
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then XML_C14N_1_0
|
12
|
+
when "http://www.w3.org/2001/10/xml-exc-c14n" then XML_C14N_EXCLUSIVE_1_0
|
13
|
+
when "http://www.w3.org/2006/12/xml-c14n11" then XML_C14N_1_1
|
14
|
+
else raise ArgumentError, "unknown canonicalization method #{method}"
|
15
|
+
end
|
16
|
+
|
17
|
+
@with_comments = !!with_comments
|
18
|
+
end
|
19
|
+
|
20
|
+
def apply(input)
|
21
|
+
raise ArgumentError, "input #{input.inspect}:#{input.class} is not canonicalizable" unless input.respond_to?(:canonicalize)
|
22
|
+
|
23
|
+
input.canonicalize(method, nil, with_comments)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module SignedXml
|
4
|
+
module DigestMethodResolution
|
5
|
+
include OpenSSL
|
6
|
+
|
7
|
+
def new_digester_for_id(id)
|
8
|
+
id = id && id =~ /sha(.*?)$/i && $1.to_i
|
9
|
+
case id
|
10
|
+
when 256 then OpenSSL::Digest::SHA256.new
|
11
|
+
when 384 then OpenSSL::Digest::SHA384.new
|
12
|
+
when 512 then OpenSSL::Digest::SHA512.new
|
13
|
+
when 1 then OpenSSL::Digest::SHA1.new
|
14
|
+
else
|
15
|
+
raise ArgumentError, "unknown digest method #{id}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "openssl"
|
2
|
+
|
3
|
+
module SignedXml
|
4
|
+
class DigestTransform
|
5
|
+
include DigestMethodResolution
|
6
|
+
|
7
|
+
attr_reader :digest
|
8
|
+
|
9
|
+
def initialize(method_id)
|
10
|
+
@digest = new_digester_for_id(method_id)
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(input)
|
14
|
+
digest.digest(input)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "openssl"
|
2
|
+
require "options"
|
3
|
+
|
4
|
+
module SignedXml
|
5
|
+
class Document
|
6
|
+
include Logging
|
7
|
+
|
8
|
+
attr_reader :doc
|
9
|
+
|
10
|
+
def initialize(thing)
|
11
|
+
if thing.is_a? Nokogiri::XML::Document
|
12
|
+
@doc = thing
|
13
|
+
else
|
14
|
+
@doc = Nokogiri::XML(thing)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_verifiable?
|
19
|
+
signatures.any?
|
20
|
+
end
|
21
|
+
|
22
|
+
def is_verified?(arg = nil)
|
23
|
+
unless is_verifiable?
|
24
|
+
logger.warn "document cannot be verified because it contains no <Signature> elements"
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
if arg.respond_to? :public_key
|
29
|
+
set_public_key_for_signatures(arg)
|
30
|
+
elsif arg.respond_to? :has_key?
|
31
|
+
set_certificate_store_for_signatures(arg)
|
32
|
+
elsif !arg.nil?
|
33
|
+
raise ArgumentError, "#{arg.inspect}:#{arg.class} must have a public key or be a hash of public keys"
|
34
|
+
end
|
35
|
+
|
36
|
+
signatures.all?(&:is_verified?)
|
37
|
+
end
|
38
|
+
|
39
|
+
def sign(private_key, certificate = nil)
|
40
|
+
signatures.each { |sig| sig.sign(private_key, certificate) }
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_xml
|
45
|
+
doc.to_xml
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def signatures
|
51
|
+
@signatures ||= init_signatures
|
52
|
+
end
|
53
|
+
|
54
|
+
def init_signatures
|
55
|
+
signatures = []
|
56
|
+
doc.xpath("//ds:Signature", ds: XMLDSIG_NS).each do |signature_node|
|
57
|
+
signatures << Signature.new(signature_node)
|
58
|
+
end
|
59
|
+
signatures
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_public_key_for_signatures(certificate)
|
63
|
+
signatures.each { |sig| sig.public_key = certificate.public_key }
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_certificate_store_for_signatures(cert_store)
|
67
|
+
signatures.each { |sig| sig.certificate_store = cert_store }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module SignedXml
|
2
|
+
class Reference
|
3
|
+
include Transformable
|
4
|
+
include Logging
|
5
|
+
|
6
|
+
attr_reader :here, :start
|
7
|
+
|
8
|
+
def initialize(here)
|
9
|
+
@here = here
|
10
|
+
|
11
|
+
uri = here['URI']
|
12
|
+
case uri
|
13
|
+
when nil, ""
|
14
|
+
@start = here.document.root
|
15
|
+
when /^#/
|
16
|
+
id = uri.split('#').last
|
17
|
+
raise ArgumentError, "XPointer expressions like #{id} are not yet supported" if id =~ /^xpointer/
|
18
|
+
# TODO: handle ID attrs with names other than 'ID'
|
19
|
+
@start = here.document.at_xpath("//*[@ID='#{id}']")
|
20
|
+
raise ArgumentError, "no match found for ID #{id}" if @start.nil?
|
21
|
+
else
|
22
|
+
raise ArgumentError, "unsupported Reference URI #{uri}"
|
23
|
+
end
|
24
|
+
|
25
|
+
@transforms = init_transforms
|
26
|
+
end
|
27
|
+
|
28
|
+
def is_verified?
|
29
|
+
result = apply_transforms == digest_value
|
30
|
+
logger.info "verification failed for digest value [#{digest_value}]" unless result
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def compute_and_set_digest_value
|
35
|
+
digest_value_node.content = apply_transforms
|
36
|
+
logger.debug "set digest value to [#{digest_value}]"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def init_transforms
|
42
|
+
transforms = []
|
43
|
+
|
44
|
+
here.xpath('.//ds:Transform', ds: XMLDSIG_NS).each do |transform_node|
|
45
|
+
method = transform_node['Algorithm']
|
46
|
+
case method
|
47
|
+
when "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
48
|
+
transforms << EnvelopedSignatureTransform.new
|
49
|
+
when %r{^http://.*c14n}
|
50
|
+
transforms << C14NTransform.new(method)
|
51
|
+
else
|
52
|
+
raise ArgumentError, "unknown transform method #{method}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# If no explicit c14n transform is specified, make sure we do one before digesting.
|
57
|
+
transforms << C14NTransform.new unless transforms.last.is_a? C14NTransform
|
58
|
+
|
59
|
+
digest_method = here.at_xpath('//ds:DigestMethod/@Algorithm', ds: XMLDSIG_NS).value.strip
|
60
|
+
transforms << DigestTransform.new(digest_method)
|
61
|
+
|
62
|
+
transforms << Base64Transform.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def digest_value
|
66
|
+
@digest_value ||= digest_value_node.text.strip
|
67
|
+
end
|
68
|
+
|
69
|
+
def digest_value_node
|
70
|
+
@digest_value_node ||= here.at_xpath('ds:DigestValue', ds: XMLDSIG_NS)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module SignedXml
|
4
|
+
class Signature
|
5
|
+
include DigestMethodResolution
|
6
|
+
include Fingerprinting
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
attr_accessor :here
|
10
|
+
attr_accessor :public_key
|
11
|
+
attr_accessor :certificate_store
|
12
|
+
|
13
|
+
def initialize(here)
|
14
|
+
@here = here
|
15
|
+
end
|
16
|
+
|
17
|
+
def is_verified?
|
18
|
+
is_signed_info_verified? && are_reference_digests_verified?
|
19
|
+
end
|
20
|
+
|
21
|
+
def sign(private_key, certificate = nil)
|
22
|
+
compute_digests_for_references
|
23
|
+
sign_signed_info(private_key)
|
24
|
+
set_certificate_data(certificate)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def is_signed_info_verified?
|
30
|
+
return false if public_key.nil?
|
31
|
+
|
32
|
+
result = public_key.verify(new_digester_for_id(signed_info.signature_method), decoded_value, signed_info.apply_transforms)
|
33
|
+
logger.info "verification of signature value [#{value}] failed" unless result
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
def are_reference_digests_verified?
|
38
|
+
references.all?(&:is_verified?)
|
39
|
+
end
|
40
|
+
|
41
|
+
def sign_signed_info(private_key)
|
42
|
+
data = signed_info.apply_transforms
|
43
|
+
logger.debug "data to sign: [#{data}]"
|
44
|
+
digester = new_digester_for_id(signed_info.signature_method)
|
45
|
+
logger.debug "digester: #{digester.inspect}"
|
46
|
+
sig_value = private_key.sign(digester, data)
|
47
|
+
logger.debug "signature value (before Base64 encoding): [#{sig_value}]"
|
48
|
+
value_node.content = Base64Transform.new.apply(sig_value)
|
49
|
+
logger.debug "SignatureValue set to [#{value}]"
|
50
|
+
end
|
51
|
+
|
52
|
+
def compute_digests_for_references
|
53
|
+
references.each(&:compute_and_set_digest_value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_certificate_data(certificate)
|
57
|
+
x509_cert_data_node.content = certificate.to_pem.split("\n")[1..-2].join("\n") if certificate
|
58
|
+
logger.debug "set certificate data to [#{x509_cert_data}]"
|
59
|
+
end
|
60
|
+
|
61
|
+
def references
|
62
|
+
@references ||= init_references
|
63
|
+
end
|
64
|
+
|
65
|
+
def init_references
|
66
|
+
references = []
|
67
|
+
|
68
|
+
here.xpath('//ds:Reference', ds: XMLDSIG_NS).each do |reference_node|
|
69
|
+
references << Reference.new(reference_node)
|
70
|
+
end
|
71
|
+
|
72
|
+
references
|
73
|
+
end
|
74
|
+
|
75
|
+
def decoded_value
|
76
|
+
@decoded_value ||= Base64.decode64 value
|
77
|
+
end
|
78
|
+
|
79
|
+
def value
|
80
|
+
@value ||= value_node.text.strip
|
81
|
+
end
|
82
|
+
|
83
|
+
def value_node
|
84
|
+
@value_node ||= here.at_xpath('//ds:SignatureValue', ds: XMLDSIG_NS)
|
85
|
+
end
|
86
|
+
|
87
|
+
def signed_info
|
88
|
+
@signed_info ||= SignedInfo.new(here.at_xpath("//ds:SignedInfo", ds: XMLDSIG_NS))
|
89
|
+
end
|
90
|
+
|
91
|
+
def certificate_store
|
92
|
+
@certificate_store ||= {}
|
93
|
+
end
|
94
|
+
|
95
|
+
def public_key
|
96
|
+
# If the user provided a certificate store, we MUST only use a
|
97
|
+
# key which matches the one in the signature's KeyInfo. Otherwise,
|
98
|
+
# use the key in the signature.
|
99
|
+
@public_key ||= if certificate_store.any?
|
100
|
+
logger.debug "using cert store #{certificate_store}"
|
101
|
+
if certificate_store.has_key? x509_cert_fingerprint
|
102
|
+
certificate_store[x509_cert_fingerprint].public_key
|
103
|
+
else
|
104
|
+
logger.warn "Store has no certificate with fingerprint #{x509_cert_fingerprint}. Signature validation will fail."
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
else
|
108
|
+
x509_certificate.public_key
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def x509_certificate
|
113
|
+
OpenSSL::X509::Certificate.new(certificate(x509_cert_data))
|
114
|
+
end
|
115
|
+
|
116
|
+
def x509_cert_fingerprint
|
117
|
+
@x509_cert_fingerprint ||= fingerprint(x509_certificate.to_der)
|
118
|
+
end
|
119
|
+
|
120
|
+
def x509_cert_data
|
121
|
+
x509_cert_data_node.text
|
122
|
+
end
|
123
|
+
|
124
|
+
def x509_cert_data_node
|
125
|
+
@x509_cert_data_node ||= here.at_xpath("//ds:X509Certificate", ds: XMLDSIG_NS)
|
126
|
+
end
|
127
|
+
|
128
|
+
def certificate(data)
|
129
|
+
"-----BEGIN CERTIFICATE-----\n#{wrap_text(data, 64)}-----END CERTIFICATE-----\n"
|
130
|
+
end
|
131
|
+
|
132
|
+
# http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
|
133
|
+
def wrap_text(txt, col = 80)
|
134
|
+
txt.gsub(/(.{1,#{col}})( +|$)\n?|(.{#{col}})/, "\\1\\3\n")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SignedXml
|
2
|
+
class SignedInfo
|
3
|
+
include Transformable
|
4
|
+
|
5
|
+
attr_reader :start, :signature_method
|
6
|
+
|
7
|
+
def initialize(here)
|
8
|
+
@start = here
|
9
|
+
|
10
|
+
canonicalization_method = here.at_xpath('//ds:CanonicalizationMethod/@Algorithm', ds: XMLDSIG_NS).value.strip
|
11
|
+
|
12
|
+
transforms << C14NTransform.new(canonicalization_method)
|
13
|
+
|
14
|
+
@signature_method = here.at_xpath('//ds:SignatureMethod/@Algorithm', ds: XMLDSIG_NS).value.strip
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module SignedXml
|
2
|
+
module Transformable
|
3
|
+
include Logging
|
4
|
+
|
5
|
+
def transforms
|
6
|
+
@transforms ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply_transforms
|
10
|
+
transforms.reduce(start) do |input, transform|
|
11
|
+
logger.debug "applying transform #{transform.inspect}"
|
12
|
+
logger.debug "input: [#{input}]"
|
13
|
+
|
14
|
+
result = transform.apply(input)
|
15
|
+
logger.debug "output: [#{result}]"
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: omniauth-islykill
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.8
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bjorgvin Thordarson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: omniauth
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ruby-saml
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.7.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.7.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: options
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: This is a specific SAML strategy that handles authentication to Icelands
|
70
|
+
Íslykill for OmniAuth.
|
71
|
+
email: algrim.is@outlook.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- README.md
|
77
|
+
- CHANGELOG.md
|
78
|
+
- lib/signed_xml.rb
|
79
|
+
- lib/signed_xml/c14n_transform.rb
|
80
|
+
- lib/signed_xml/signed_info.rb
|
81
|
+
- lib/signed_xml/digest_transform.rb
|
82
|
+
- lib/signed_xml/document.rb
|
83
|
+
- lib/signed_xml/enveloped_signature_transform.rb
|
84
|
+
- lib/signed_xml/reference.rb
|
85
|
+
- lib/signed_xml/base64_transform.rb
|
86
|
+
- lib/signed_xml/version.rb
|
87
|
+
- lib/signed_xml/digest_method_resolution.rb
|
88
|
+
- lib/signed_xml/logging.rb
|
89
|
+
- lib/signed_xml/fingerprinting.rb
|
90
|
+
- lib/signed_xml/transformable.rb
|
91
|
+
- lib/signed_xml/signature.rb
|
92
|
+
- lib/omniauth-islykill.rb
|
93
|
+
- lib/omniauth-islykill/version.rb
|
94
|
+
- lib/omniauth/strategies/islykill/validation_error.rb
|
95
|
+
- lib/omniauth/strategies/islykill.rb
|
96
|
+
homepage: https://github.com/Algrim/omniauth-islykill
|
97
|
+
licenses:
|
98
|
+
- ''
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.1.9
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: This is a specific SAML strategy that handles authentication to Icelands
|
120
|
+
Íslykill for OmniAuth.
|
121
|
+
test_files: []
|