Class: OneLogin::RubySaml::Utils

Inherits:
Object
  • Object
show all
Defined in:
lib/onelogin/ruby-saml/utils.rb

Overview

SAML2 Auxiliary class

Constant Summary collapse

DSIG =
"http://www.w3.org/2000/09/xmldsig#"
XENC =
"http://www.w3.org/2001/04/xmlenc#"

Class Method Summary collapse

Class Method Details

.build_query(params) ⇒ String

Build the Query String signature that will be used in the HTTP-Redirect binding to generate the Signature

Parameters:

  • params (Hash)

    Parameters to build the Query String

Options Hash (params):

  • :type (String)

    ‘SAMLRequest’ or ‘SAMLResponse’

  • :data (String)

    Base64 encoded SAMLRequest or SAMLResponse

  • :relay_state (String)

    The RelayState parameter

  • :sig_alg (String)

    The SigAlg parameter

Returns:

  • (String)

    The Query String



55
56
57
58
59
60
61
# File 'lib/onelogin/ruby-saml/utils.rb', line 55

def self.build_query(params)
  type, data, relay_state, sig_alg = [:type, :data, :relay_state, :sig_alg].map { |k| params[k]}

  url_string = "#{type}=#{CGI.escape(data)}"
  url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
  url_string << "&SigAlg=#{CGI.escape(sig_alg)}"
end

.decrypt_data(encrypted_node, private_key) ⇒ String

Obtains the decrypted string from an Encrypted node element in XML

Parameters:

  • encrypted_node (REXML::Element)

    The Encrypted element

  • private_key (OpenSSL::PKey::RSA)

    The Service provider private key

Returns:

  • (String)

    The decrypted data



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/onelogin/ruby-saml/utils.rb', line 98

def self.decrypt_data(encrypted_node, private_key)
  encrypt_data = REXML::XPath.first(
    encrypted_node,
    "./xenc:EncryptedData",
    { 'xenc' => XENC }
  )
  symmetric_key = retrieve_symmetric_key(encrypt_data, private_key)
  cipher_value = REXML::XPath.first(
    encrypt_data,
    "//xenc:EncryptedData/xenc:CipherData/xenc:CipherValue",
    { 'xenc' => XENC }
  )
  node = Base64.decode64(cipher_value.text)
  encrypt_method = REXML::XPath.first(
    encrypt_data,
    "//xenc:EncryptedData/xenc:EncryptionMethod",
    { 'xenc' => XENC }
  )
  algorithm = encrypt_method.attributes['Algorithm']
  retrieve_plaintext(node, symmetric_key, algorithm)        
end

.format_cert(cert) ⇒ String

Return a properly formatted x509 certificate

Parameters:

  • cert (String)

    The original certificate

Returns:

  • (String)

    The formatted certificate



16
17
18
19
20
21
22
23
24
25
# File 'lib/onelogin/ruby-saml/utils.rb', line 16

def self.format_cert(cert)
  # don't try to format an encoded certificate or if is empty or nil
  return cert if cert.nil? || cert.empty? || cert.match(/\x0d/)

  cert = cert.gsub(/\-{5}\s?(BEGIN|END) CERTIFICATE\s?\-{5}/, "")
  cert = cert.gsub(/[\n\r\s]/, "")
  cert = cert.scan(/.{1,64}/)
  cert = cert.join("\n")
  "-----BEGIN CERTIFICATE-----\n#{cert}\n-----END CERTIFICATE-----"
end

.format_private_key(key) ⇒ String

Return a properly formatted private key

Parameters:

  • key (String)

    The original private key

Returns:

  • (String)

    The formatted private key



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/onelogin/ruby-saml/utils.rb', line 32

def self.format_private_key(key)
  # don't try to format an encoded private key or if is empty  
  return key if key.nil? || key.empty? || key.match(/\x0d/)

  # is this an rsa key?
  rsa_key = key.match("RSA PRIVATE KEY")
  key = key.gsub(/\-{5}\s?(BEGIN|END)( RSA)? PRIVATE KEY\s?\-{5}/, "")
  key = key.gsub(/[\n\r\s]/, "")
  key = key.scan(/.{1,64}/)
  key = key.join("\n")
  key_label = rsa_key ? "RSA PRIVATE KEY" : "PRIVATE KEY"
  "-----BEGIN #{key_label}-----\n#{key}\n-----END #{key_label}-----"
end

.retrieve_plaintext(cipher_text, symmetric_key, algorithm) ⇒ String

Obtains the deciphered text

Parameters:

  • cipher_text (String)

    The ciphered text

  • symmetric_key (String)

    The symetric key used to encrypt the text

  • algorithm (String)

    The encrypted algorithm

Returns:

  • (String)

    The deciphered text



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/onelogin/ruby-saml/utils.rb', line 145

def self.retrieve_plaintext(cipher_text, symmetric_key, algorithm)
  case algorithm
    when 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' then cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
    when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
    when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
    when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
    when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
    when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
  end

  if cipher          
    iv_len = cipher.iv_len
    data = cipher_text[iv_len..-1]
    cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
    assertion_plaintext = cipher.update(data)
    assertion_plaintext << cipher.final
  elsif rsa
    rsa.private_decrypt(cipher_text)
  elsif oaep
    oaep.private_decrypt(cipher_text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
  else
    cipher_text
  end
end

.retrieve_symmetric_key(encrypt_data, private_key) ⇒ String

Obtains the symmetric key from the EncryptedData element

Parameters:

  • encrypt_data (REXML::Element)

    The EncryptedData element

  • private_key (OpenSSL::PKey::RSA)

    The Service provider private key

Returns:

  • (String)

    The symmetric key



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/onelogin/ruby-saml/utils.rb', line 124

def self.retrieve_symmetric_key(encrypt_data, private_key)
  encrypted_symmetric_key_element = REXML::XPath.first(
    encrypt_data,
    "//xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey/xenc:CipherData/xenc:CipherValue",
    { "ds" => DSIG, "xenc" => XENC }
  )
  cipher_text = Base64.decode64(encrypted_symmetric_key_element.text)
  encrypt_method = REXML::XPath.first(
    encrypt_data,
    "//xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey/xenc:EncryptionMethod",
    {"ds" => DSIG,  "xenc" => XENC }
  )
  algorithm = encrypt_method.attributes['Algorithm']
  retrieve_plaintext(cipher_text, private_key, algorithm)        
end

.status_error_msg(error_msg, status_code = nil, status_message = nil) ⇒ String

Build the status error message

Parameters:

  • status_code (String) (defaults to: nil)

    StatusCode value

  • status_message (Strig) (defaults to: nil)

    StatusMessage value

Returns:

  • (String)

    The status error message



81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/onelogin/ruby-saml/utils.rb', line 81

def self.status_error_msg(error_msg, status_code = nil, status_message = nil)
  unless status_code.nil?
    printable_code = status_code.split(':').last
    error_msg << ', was ' + printable_code
  end

  unless status_message.nil?
    error_msg << ' -> ' + status_message
  end

  error_msg
end

.verify_signature(params) ⇒ Boolean

Validate the Signature parameter sent on the HTTP-Redirect binding

Parameters:

  • params (Hash)

    Parameters to be used in the validation process

Options Hash (params):

  • cert (OpenSSL::X509::Certificate)

    The Identity provider public certtificate

  • sig_alg (String)

    The SigAlg parameter

  • signature (String)

    The Signature parameter (base64 encoded)

  • query_string (String)

    The SigAlg parameter

Returns:

  • (Boolean)

    True if the Signature is valid, False otherwise



71
72
73
74
75
# File 'lib/onelogin/ruby-saml/utils.rb', line 71

def self.verify_signature(params)
  cert, sig_alg, signature, query_string = [:cert, :sig_alg, :signature, :query_string].map { |k| params[k]}
  signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(sig_alg)
  return cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string)
end