omniauth-islykill 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|