xml-kit 0.1.14 → 0.5.0

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.
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