From f9fac68fe6d930078e0430125a6525eb7398f59a Mon Sep 17 00:00:00 2001 From: Marcelo de Moraes Serpa Date: Tue, 20 Jul 2010 18:07:47 -0500 Subject: [PATCH] Version bump to 0.0.1 --- VERSION | 2 +- lib/onelogin/saml.rb | 6 +++ lib/onelogin/saml/authrequest.rb | 38 +++++++++++++ lib/onelogin/saml/response.rb | 29 ++++++++++ lib/onelogin/saml/settings.rb | 45 ++++++++++++++++ lib/ruby-saml.rb | 1 + lib/xml_sec.rb | 91 ++++++++++++++++++++++++++++++++ 7 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 lib/onelogin/saml.rb create mode 100644 lib/onelogin/saml/authrequest.rb create mode 100644 lib/onelogin/saml/response.rb create mode 100644 lib/onelogin/saml/settings.rb create mode 100644 lib/xml_sec.rb diff --git a/VERSION b/VERSION index 77d6f4ca2..8acdd82b7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.0 +0.0.1 diff --git a/lib/onelogin/saml.rb b/lib/onelogin/saml.rb new file mode 100644 index 000000000..edaeff52b --- /dev/null +++ b/lib/onelogin/saml.rb @@ -0,0 +1,6 @@ +require 'onelogin/saml/authrequest' +require 'onelogin/saml/response' +require 'onelogin/saml/settings' + +module Onelogin::Saml +end \ No newline at end of file diff --git a/lib/onelogin/saml/authrequest.rb b/lib/onelogin/saml/authrequest.rb new file mode 100644 index 000000000..d3b906dae --- /dev/null +++ b/lib/onelogin/saml/authrequest.rb @@ -0,0 +1,38 @@ +require "base64" + +module Onelogin::Saml + class Authrequest + def create(settings) + id = Onelogin::Saml::Authrequest.generateUniqueID(42) + issue_instant = Onelogin::Saml::Authrequest.getTimestamp + + request = + "" + + "#{settings.issuer}\n" + + "\n" + + "" + + "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport\n" + + "" + + deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5] + base64_request = Base64.encode64(deflated_request) + encoded_request = CGI.escape(base64_request) + + settings.idp_sso_target_url + "?SAMLRequest=" + encoded_request + end + + private + + def self.generateUniqueID(length) + chars = ("a".."f").to_a + ("0".."9").to_a + chars_len = chars.size + uniqueID = "" + 1.upto(length) { |i| uniqueID << chars[rand(chars_len-1)] } + uniqueID + end + + def self.getTimestamp + Time.new().strftime("%Y-%m-%dT%H:%M:%SZ") + end + end +end \ No newline at end of file diff --git a/lib/onelogin/saml/response.rb b/lib/onelogin/saml/response.rb new file mode 100644 index 000000000..e25131cc0 --- /dev/null +++ b/lib/onelogin/saml/response.rb @@ -0,0 +1,29 @@ +require "rexml/document" +require "xml_sec" + +module Onelogin::Saml + class Response + def initialize(response) + @response = response + @document = XMLSecurity::SignedDocument.new(Base64.decode64(@response)) + end + + def logger=(val) + @logger = val + end + + def settings=(_settings) + @settings = _settings + end + + def is_valid? + unless @response.blank? + @document.validate(@settings.idp_cert_fingerprint, @logger) unless !@settings.idp_cert_fingerprint + end + end + + def name_id + @document.elements["/samlp:Response/saml:Assertion/saml:Subject/saml:NameID"].text + end + end +end \ No newline at end of file diff --git a/lib/onelogin/saml/settings.rb b/lib/onelogin/saml/settings.rb new file mode 100644 index 000000000..401f452e2 --- /dev/null +++ b/lib/onelogin/saml/settings.rb @@ -0,0 +1,45 @@ +module Onelogin::Saml + class Settings + def assertion_consumer_service_url + @assertion_consumer_service_url + end + def assertion_consumer_service_url=(val) + @assertion_consumer_service_url = val + end + + def issuer + @issuer + end + def issuer=(val) + @issuer = val + end + + def sp_name_qualifier + @sp_name_qualifier + end + def sp_name_qualifier=(val) + @sp_name_qualifier = val + end + + def idp_sso_target_url + @idp_sso_target_url + end + def idp_sso_target_url=(val) + @idp_sso_target_url = val + end + + def idp_cert_fingerprint + @idp_cert_fingerprint + end + def idp_cert_fingerprint=(val) + @idp_cert_fingerprint = val + end + + def name_identifier_format + @name_identifier_format + end + def name_identifier_format=(val) + @name_identifier_format = val + end + end +end \ No newline at end of file diff --git a/lib/ruby-saml.rb b/lib/ruby-saml.rb index e69de29bb..9997cfe46 100644 --- a/lib/ruby-saml.rb +++ b/lib/ruby-saml.rb @@ -0,0 +1 @@ +require 'onelogin/saml' diff --git a/lib/xml_sec.rb b/lib/xml_sec.rb new file mode 100644 index 000000000..2346a4a4c --- /dev/null +++ b/lib/xml_sec.rb @@ -0,0 +1,91 @@ +# The contents of this file are subject to the terms +# of the Common Development and Distribution License +# (the License). You may not use this file except in +# compliance with the License. +# +# You can obtain a copy of the License at +# https://opensso.dev.java.net/public/CDDLv1.0.html or +# opensso/legal/CDDLv1.0.txt +# See the License for the specific language governing +# permission and limitations under the License. +# +# When distributing Covered Code, include this CDDL +# Header Notice in each file and include the License file +# at opensso/legal/CDDLv1.0.txt. +# If applicable, add the following below the CDDL Header, +# with the fields enclosed by brackets [] replaced by +# your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# +# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $ +# +# Copyright 2007 Sun Microsystems Inc. All Rights Reserved +# Portions Copyrighted 2007 Todd W Saxton. + +require 'rubygems' +require "rexml/document" +require "rexml/xpath" +require "openssl" +require "xmlcanonicalizer" +require "digest/sha1" + +module XMLSecurity + + class SignedDocument < REXML::Document + + def validate (idp_cert_fingerprint, logger = nil) + # get cert from response + base64_cert = self.elements["//ds:X509Certificate"].text + cert_text = Base64.decode64(base64_cert) + cert = OpenSSL::X509::Certificate.new(cert_text) + + # check cert matches registered idp cert + fingerprint = Digest::SHA1.hexdigest(cert.to_der) + valid_flag = fingerprint == idp_cert_fingerprint.gsub(":", "").downcase + + return valid_flag if !valid_flag + + validate_doc(base64_cert, logger) + end + + def validate_doc(base64_cert, logger) + # validate references + + # remove signature node + sig_element = XPath.first(self, "//ds:Signature", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}) + sig_element.remove + + #check digests + XPath.each(sig_element, "//ds:Reference", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}) do | ref | + + uri = ref.attributes.get_attribute("URI").value + hashed_element = XPath.first(self, "//[@ID='#{uri[1,uri.size]}']") + canoner = XML::Util::XmlCanonicalizer.new(false, true) + canon_hashed_element = canoner.canonicalize(hashed_element) + hash = Base64.encode64(Digest::SHA1.digest(canon_hashed_element)).chomp + digest_value = XPath.first(ref, "//ds:DigestValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text + + valid_flag = hash == digest_value + + return valid_flag if !valid_flag + end + + # verify signature + canoner = XML::Util::XmlCanonicalizer.new(false, true) + signed_info_element = XPath.first(sig_element, "//ds:SignedInfo", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}) + canon_string = canoner.canonicalize(signed_info_element) + + base64_signature = XPath.first(sig_element, "//ds:SignatureValue", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}).text + signature = Base64.decode64(base64_signature) + + # get certificate object + cert_text = Base64.decode64(base64_cert) + cert = OpenSSL::X509::Certificate.new(cert_text) + + valid_flag = cert.public_key.verify(OpenSSL::Digest::SHA1.new, signature, canon_string) + + return valid_flag + end + + end +end