xml-kit 0.1.14 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +23 -5
  3. data/.travis.yml +7 -5
  4. data/CHANGELOG.md +60 -0
  5. data/README.md +14 -22
  6. data/bin/cibuild +1 -1
  7. data/lib/xml/kit.rb +11 -2
  8. data/lib/xml/kit/certificate.rb +6 -4
  9. data/lib/xml/kit/crypto.rb +14 -0
  10. data/lib/xml/kit/crypto/oaep_cipher.rb +5 -2
  11. data/lib/xml/kit/crypto/rsa_cipher.rb +4 -2
  12. data/lib/xml/kit/crypto/symmetric_cipher.rb +30 -9
  13. data/lib/xml/kit/crypto/unknown_cipher.rb +6 -1
  14. data/lib/xml/kit/decryption.rb +29 -20
  15. data/lib/xml/kit/document.rb +5 -4
  16. data/lib/xml/kit/encrypted_data.rb +51 -0
  17. data/lib/xml/kit/encrypted_key.rb +35 -0
  18. data/lib/xml/kit/encryption.rb +27 -18
  19. data/lib/xml/kit/fingerprint.rb +1 -1
  20. data/lib/xml/kit/key_info.rb +71 -0
  21. data/lib/xml/kit/key_info/key_value.rb +19 -0
  22. data/lib/xml/kit/key_info/retrieval_method.rb +19 -0
  23. data/lib/xml/kit/key_info/rsa_key_value.rb +15 -0
  24. data/lib/xml/kit/key_pair.rb +8 -3
  25. data/lib/xml/kit/namespaces.rb +12 -12
  26. data/lib/xml/kit/self_signed_certificate.rb +16 -3
  27. data/lib/xml/kit/signature.rb +9 -2
  28. data/lib/xml/kit/signatures.rb +4 -1
  29. data/lib/xml/kit/templatable.rb +75 -24
  30. data/lib/xml/kit/templates/certificate.builder +1 -5
  31. data/lib/xml/kit/templates/encrypted_data.builder +9 -0
  32. data/lib/xml/kit/templates/encrypted_key.builder +9 -0
  33. data/lib/xml/kit/templates/key_info.builder +14 -0
  34. data/lib/xml/kit/templates/key_value.builder +5 -0
  35. data/lib/xml/kit/templates/retrieval_method.builder +3 -0
  36. data/lib/xml/kit/templates/rsa_key_value.builder +6 -0
  37. data/lib/xml/kit/templates/signature.builder +1 -1
  38. data/lib/xml/kit/version.rb +1 -1
  39. data/xml-kit.gemspec +4 -4
  40. metadata +29 -18
  41. data/.rubocop_todo.yml +0 -22
  42. data/lib/xml/kit/templates/encryption.builder +0 -16
@@ -4,7 +4,12 @@ module Xml
4
4
  module Kit
5
5
  module Crypto
6
6
  class UnknownCipher
7
- def initialize(algorithm, key); end
7
+ attr_reader :algorithm, :key
8
+
9
+ def initialize(algorithm, key)
10
+ @algorithm = algorithm
11
+ @key = key
12
+ end
8
13
 
9
14
  def self.matches?(_algorithm)
10
15
  true
@@ -2,20 +2,24 @@
2
2
 
3
3
  module Xml
4
4
  module Kit
5
- # {include:file:spec/saml/xml_decryption_spec.rb}
5
+ # {include:file:spec/xml/kit/decryption_spec.rb}
6
6
  class Decryption
7
7
  # The list of private keys to use to attempt to decrypt the document.
8
- attr_reader :private_keys
8
+ attr_reader :cipher_registry, :private_keys
9
9
 
10
- def initialize(private_keys:)
10
+ def initialize(private_keys:, cipher_registry: ::Xml::Kit::Crypto)
11
11
  @private_keys = private_keys
12
+ @cipher_registry = cipher_registry
12
13
  end
13
14
 
14
15
  # Decrypts an EncryptedData section of an XML document.
15
16
  #
16
17
  # @param data [Hash] the XML document converted to a [Hash] using Hash.from_xml.
18
+ # @deprecated Use {#decrypt_hash} instead of this
17
19
  def decrypt(data)
18
- ::Xml::Kit.deprecate('decrypt is deprecated. Use decrypt_xml or decrypt_hash instead.')
20
+ ::Xml::Kit.deprecate(
21
+ 'decrypt is deprecated. Use decrypt_xml or decrypt_hash instead.'
22
+ )
19
23
  decrypt_hash(data)
20
24
  end
21
25
 
@@ -30,12 +34,12 @@ module Xml
30
34
  #
31
35
  # @param hash [Hash] the XML document converted to a [Hash] using Hash.from_xml.
32
36
  def decrypt_hash(hash)
33
- encrypted_data = hash['EncryptedData']
34
- symmetric_key = symmetric_key_from(encrypted_data)
35
- cipher_value = encrypted_data['CipherData']['CipherValue']
36
- cipher_text = Base64.decode64(cipher_value)
37
- algorithm = encrypted_data['EncryptionMethod']['Algorithm']
38
- to_plaintext(cipher_text, symmetric_key, algorithm)
37
+ data = hash['EncryptedData']
38
+ to_plaintext(
39
+ Base64.decode64(data['CipherData']['CipherValue']),
40
+ symmetric_key_from(data['KeyInfo']['EncryptedKey']),
41
+ data['EncryptionMethod']['Algorithm']
42
+ )
39
43
  end
40
44
 
41
45
  # Decrypts an EncryptedData Nokogiri::XML::Element.
@@ -49,21 +53,26 @@ module Xml
49
53
 
50
54
  private
51
55
 
52
- def symmetric_key_from(encrypted_data, attempts = private_keys.count)
53
- cipher_text = Base64.decode64(encrypted_data['KeyInfo']['EncryptedKey']['CipherData']['CipherValue'])
56
+ def symmetric_key_from(encrypted_key, attempts = private_keys.count)
57
+ cipher, algorithm = cipher_and_algorithm_from(encrypted_key)
54
58
  private_keys.each do |private_key|
55
- begin
56
- attempts -= 1
57
- return to_plaintext(cipher_text, private_key, encrypted_data['KeyInfo']['EncryptedKey']['EncryptionMethod']['Algorithm'])
58
- rescue OpenSSL::PKey::RSAError
59
- raise if attempts.zero?
60
- end
59
+ attempts -= 1
60
+ return to_plaintext(cipher, private_key, algorithm)
61
+ rescue OpenSSL::PKey::RSAError
62
+ raise if attempts.zero?
61
63
  end
62
64
  raise DecryptionError, private_keys
63
65
  end
64
66
 
65
- def to_plaintext(cipher_text, symmetric_key, algorithm)
66
- Crypto.cipher_for(algorithm, symmetric_key).decrypt(cipher_text)
67
+ def to_plaintext(cipher_text, private_key, algorithm)
68
+ cipher_registry.cipher_for(algorithm, private_key).decrypt(cipher_text)
69
+ end
70
+
71
+ def cipher_and_algorithm_from(encrypted_key)
72
+ [
73
+ Base64.decode64(encrypted_key['CipherData']['CipherValue']),
74
+ encrypted_key['EncryptionMethod']['Algorithm']
75
+ ]
67
76
  end
68
77
  end
69
78
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Xml
4
4
  module Kit
5
- # {include:file:spec/saml/xml_spec.rb}
5
+ # {include:file:spec/xml/kit/document_spec.rb}
6
6
  class Document
7
7
  include ActiveModel::Validations
8
8
  NAMESPACES = { "ds": ::Xml::Kit::Namespaces::XMLDSIG }.freeze
@@ -47,9 +47,10 @@ module Xml
47
47
  end
48
48
  end
49
49
 
50
- def invalid_signatures
51
- signed_document = Xmldsig::SignedDocument.new(document, id_attr: 'ID=$uri or @Id')
52
- signed_document.signatures.find_all do |signature|
50
+ def invalid_signatures(id_attr: 'ID=$uri or @Id')
51
+ Xmldsig::SignedDocument
52
+ .new(document, id_attr: id_attr)
53
+ .signatures.find_all do |signature|
53
54
  x509_certificates.all? do |certificate|
54
55
  !signature.valid?(certificate)
55
56
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xml
4
+ module Kit
5
+ # An implementation of the EncryptedKey element.
6
+ # https://www.w3.org/TR/xmlenc-core1/#sec-EncryptedData
7
+ #
8
+ # @since 0.3.0
9
+ class EncryptedData
10
+ attr_reader :id
11
+ attr_reader :key_info
12
+ attr_reader :symmetric_cipher
13
+ attr_reader :symmetric_cipher_value
14
+
15
+ def initialize(raw_xml,
16
+ id: Id.generate,
17
+ symmetric_cipher: nil,
18
+ asymmetric_cipher: nil,
19
+ key_info: nil)
20
+ @id = id
21
+ @symmetric_cipher = symmetric_cipher ||
22
+ key_info.try(:symmetric_cipher) ||
23
+ Xml::Kit::Crypto::SymmetricCipher.new
24
+ @symmetric_cipher_value = Base64.strict_encode64(
25
+ @symmetric_cipher.encrypt(raw_xml)
26
+ )
27
+ @key_info = key_info ||
28
+ create_key_info_for(@symmetric_cipher, asymmetric_cipher)
29
+ end
30
+
31
+ def to_xml(xml: ::Builder::XmlMarkup.new)
32
+ ::Xml::Kit::Template.new(self).to_xml(xml: xml)
33
+ end
34
+
35
+ def render(model, options)
36
+ ::Xml::Kit::Template.new(model).to_xml(options)
37
+ end
38
+
39
+ private
40
+
41
+ def create_key_info_for(symmetric_cipher, asymmetric_cipher)
42
+ KeyInfo.new do |x|
43
+ x.encrypted_key = EncryptedKey.new(
44
+ asymmetric_cipher: asymmetric_cipher,
45
+ symmetric_cipher: symmetric_cipher
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xml/kit/templatable'
4
+
5
+ module Xml
6
+ module Kit
7
+ # An implementation of the EncryptedKey element.
8
+ # https://www.w3.org/TR/xmlenc-core1/#sec-EncryptedKey
9
+ #
10
+ # @since 0.3.0
11
+ class EncryptedKey
12
+ include ::Xml::Kit::Templatable
13
+ attr_reader :id
14
+ attr_reader :asymmetric_cipher, :symmetric_cipher
15
+ attr_accessor :key_info
16
+
17
+ def initialize(id: Id.generate,
18
+ asymmetric_cipher: nil,
19
+ symmetric_cipher: nil,
20
+ key_info: nil)
21
+ @id = id
22
+ @asymmetric_cipher = asymmetric_cipher ||
23
+ key_info.try(:asymmetric_cipher)
24
+ @symmetric_cipher = symmetric_cipher ||
25
+ key_info.try(:symmetric_cipher) ||
26
+ Xml::Kit::Crypto::SymmetricCipher.new
27
+ @key_info = key_info
28
+ end
29
+
30
+ def cipher_value
31
+ Base64.strict_encode64(asymmetric_cipher.encrypt(symmetric_cipher.key))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -2,36 +2,45 @@
2
2
 
3
3
  module Xml
4
4
  module Kit
5
- class Encryption
5
+ # @deprecated Use {#Xml::Kit::EncryptedData} class instead of this
6
+ class Encryption < EncryptedData
7
+ DEFAULT_SYMMETRIC = Crypto::SymmetricCipher::DEFAULT_ALGORITHM
8
+ DEFAULT_ASYMMETRIC = Crypto::RsaCipher::ALGORITHM
9
+
6
10
  attr_reader :asymmetric_algorithm
7
- attr_reader :asymmetric_cipher_value
8
11
  attr_reader :symmetric_algorithm
9
12
  attr_reader :symmetric_cipher_value
13
+ attr_reader :key_info
10
14
 
11
- def initialize(
12
- raw_xml,
13
- public_key,
14
- symmetric_algorithm: ::Xml::Kit::Crypto::SymmetricCipher::DEFAULT_ALGORITHM,
15
- asymmetric_algorithm: ::Xml::Kit::Crypto::RsaCipher::ALGORITHM
16
- )
15
+ def initialize(raw_xml, public_key,
16
+ symmetric_algorithm: DEFAULT_SYMMETRIC,
17
+ asymmetric_algorithm: DEFAULT_ASYMMETRIC, key_info: nil)
17
18
  @symmetric_algorithm = symmetric_algorithm
18
- @symmetric_cipher_value = Base64.encode64(symmetric_cipher.encrypt(raw_xml)).delete("\n")
19
-
20
19
  @asymmetric_algorithm = asymmetric_algorithm
21
- cipher = Crypto.cipher_for(asymmetric_algorithm, public_key)
22
- @asymmetric_cipher_value = Base64.encode64(cipher.encrypt(symmetric_cipher.key)).delete("\n")
20
+ Xml::Kit.deprecate('Encryption', alternative: 'EncryptedData')
21
+ super(raw_xml,
22
+ symmetric_cipher: symmetric(symmetric_algorithm),
23
+ asymmetric_cipher: asymmetric(asymmetric_algorithm, public_key),
24
+ key_info: key_info
25
+ )
23
26
  end
24
27
 
25
- def to_xml(xml: ::Builder::XmlMarkup.new)
26
- ::Xml::Kit::Template.new(self).to_xml(xml: xml)
28
+ def template_path
29
+ Template::TEMPLATES_DIR.join('encrypted_data.builder')
27
30
  end
28
31
 
29
32
  private
30
33
 
31
- def symmetric_cipher
32
- @symmetric_cipher ||= ::Xml::Kit::Crypto::SymmetricCipher.new(
33
- symmetric_algorithm
34
- )
34
+ def symmetric(algorithm)
35
+ return algorithm unless algorithm.is_a?(String)
36
+
37
+ ::Xml::Kit::Crypto::SymmetricCipher.new(algorithm)
38
+ end
39
+
40
+ def asymmetric(algorithm, public_key)
41
+ return algorithm unless algorithm.is_a?(String)
42
+
43
+ ::Xml::Kit::Crypto.cipher_for(algorithm, public_key)
35
44
  end
36
45
  end
37
46
  end
@@ -9,7 +9,7 @@ module Xml
9
9
  # puts Xml::Kit::Fingerprint.new(certificate).to_s
10
10
  # # B7:AB:DC:BD:4D:23:58:65:FD:1A:99:0C:5F:89:EA:87:AD:F1:D7:83:34:7A:E9:E4:88:12:DD:46:1F:38:05:93
11
11
  #
12
- # {include:file:spec/saml/fingerprint_spec.rb}
12
+ # {include:file:spec/xml/kit/fingerprint_spec.rb}
13
13
  class Fingerprint
14
14
  # The OpenSSL::X509::Certificate
15
15
  attr_reader :x509
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xml/kit/key_info/key_value'
4
+ require 'xml/kit/key_info/retrieval_method'
5
+ require 'xml/kit/key_info/rsa_key_value'
6
+
7
+ module Xml
8
+ module Kit
9
+ # An implementation of the KeyInfo element.
10
+ # https://www.w3.org/TR/xmldsig-core1/#sec-KeyInfo
11
+ #
12
+ # @since 0.3.0
13
+ class KeyInfo
14
+ include Templatable
15
+ attr_accessor :key_name
16
+ attr_accessor :x509_data
17
+ attr_accessor :encrypted_key
18
+
19
+ def initialize(x509: nil, encrypted_key: nil)
20
+ @encrypted_key = encrypted_key
21
+ @x509_data = x509
22
+ yield self if block_given?
23
+ end
24
+
25
+ def asymmetric_cipher(algorithm: Crypto::RsaCipher::ALGORITHM)
26
+ return encrypted_key.asymmetric_cipher if encrypted_key
27
+
28
+ if x509_data
29
+ return Crypto.cipher_for(
30
+ derive_algorithm_from(x509_data.public_key),
31
+ x509_data.public_key
32
+ )
33
+ end
34
+
35
+ super(algorithm: algorithm)
36
+ end
37
+
38
+ def symmetric_cipher
39
+ return super if encrypted_key.nil?
40
+
41
+ encrypted_key.symmetric_cipher
42
+ end
43
+
44
+ def key_value
45
+ @key_value ||= KeyValue.new
46
+ end
47
+
48
+ def retrieval_method
49
+ @retrieval_method ||= RetrievalMethod.new
50
+ end
51
+
52
+ def subject_key_identifier
53
+ ski = x509_data.extensions.find { |x| x.oid == 'subjectKeyIdentifier' }
54
+ return if ski.nil?
55
+
56
+ Base64.strict_encode64(ski.value)
57
+ end
58
+
59
+ private
60
+
61
+ def derive_algorithm_from(key)
62
+ case key
63
+ when OpenSSL::PKey::RSA
64
+ "#{::Xml::Kit::Namespaces::XMLENC}rsa-1_5"
65
+ else
66
+ raise ::Xml::Kit::Error, "#{key.try(:class)} is not supported"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xml
4
+ module Kit
5
+ class KeyInfo
6
+ # An implementation of the RSAKeyValue element.
7
+ # https://www.w3.org/TR/xmldsig-core1/#sec-KeyValue
8
+ #
9
+ # @since 0.3.0
10
+ class KeyValue
11
+ include Templatable
12
+
13
+ def rsa
14
+ @rsa ||= RSAKeyValue.new
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xml
4
+ module Kit
5
+ class KeyInfo
6
+ # An implementation of the RSAKeyValue element.
7
+ # https://www.w3.org/TR/xmldsig-core1/#sec-RetrievalMethod
8
+ #
9
+ # @since 0.3.0
10
+ class RetrievalMethod
11
+ attr_accessor :uri, :type
12
+
13
+ def initialize
14
+ @type = "#{Namespaces::XMLENC}EncryptedKey"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xml
4
+ module Kit
5
+ class KeyInfo
6
+ # An implementation of the RSAKeyValue element.
7
+ # https://www.w3.org/TR/xmldsig-core1/#sec-RSAKeyValue
8
+ #
9
+ # @since 0.3.0
10
+ class RSAKeyValue
11
+ attr_accessor :modulus, :exponent
12
+ end
13
+ end
14
+ end
15
+ end
@@ -30,9 +30,14 @@ module Xml
30
30
  # @param use [Symbol] Can be either `:signing` or `:encryption`.
31
31
  # @param passphrase [String] the passphrase to use to encrypt the private key.
32
32
  # @param algorithm [String] the symmetric algorithm to use for encrypting the private key.
33
- def self.generate(use:, passphrase: SecureRandom.uuid, algorithm: ::Xml::Kit::Crypto::SymmetricCipher::DEFAULT_ALGORITHM)
34
- algorithm = ::Xml::Kit::Crypto::SymmetricCipher::ALGORITHMS[algorithm]
35
- certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new.create(algorithm: algorithm, passphrase: passphrase)
33
+ def self.generate(use:,
34
+ passphrase: SecureRandom.uuid,
35
+ algorithm: Crypto::SymmetricCipher::DEFAULT_ALGORITHM)
36
+ algorithm = Crypto::SymmetricCipher::ALGORITHMS[algorithm]
37
+ certificate, private_key = SelfSignedCertificate.new.create(
38
+ algorithm: algorithm,
39
+ passphrase: passphrase
40
+ )
36
41
  new(certificate, private_key, passphrase, use)
37
42
  end
38
43
  end
@@ -3,18 +3,18 @@
3
3
  module Xml
4
4
  module Kit
5
5
  module Namespaces
6
- CANONICALIZATION = 'http://www.w3.org/2001/10/xml-exc-c14n#'.freeze
7
- ENVELOPED_SIG = 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'.freeze
8
- RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'.freeze
9
- RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'.freeze
10
- RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'.freeze
11
- RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'.freeze
12
- SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1'.freeze
13
- SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'.freeze
14
- SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384'.freeze
15
- SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'.freeze
16
- XMLDSIG = 'http://www.w3.org/2000/09/xmldsig#'.freeze
17
- XMLENC = 'http://www.w3.org/2001/04/xmlenc#'.freeze
6
+ CANONICALIZATION = 'http://www.w3.org/2001/10/xml-exc-c14n#'
7
+ ENVELOPED_SIG = 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'
8
+ RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
9
+ RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
10
+ RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
11
+ RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
12
+ SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1'
13
+ SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
14
+ SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384'
15
+ SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
16
+ XMLDSIG = 'http://www.w3.org/2000/09/xmldsig#'
17
+ XMLENC = 'http://www.w3.org/2001/04/xmlenc#'
18
18
  end
19
19
  end
20
20
  end