main
 1# frozen_string_literal: true
 2
 3module Xml
 4  module Kit
 5    # {include:file:spec/xml/kit/decryption_spec.rb}
 6    class Decryption
 7      # The list of private keys to use to attempt to decrypt the document.
 8      attr_reader :cipher_registry, :private_keys
 9
10      def initialize(private_keys:, cipher_registry: ::Xml::Kit::Crypto)
11        @private_keys = private_keys
12        @cipher_registry = cipher_registry
13      end
14
15      # Decrypts an EncryptedData section of an XML document.
16      #
17      # @param data [Hash] the XML document converted to a [Hash] using Hash.from_xml.
18      # @deprecated Use {#decrypt_hash} instead of this
19      def decrypt(data)
20        ::Xml::Kit.deprecate(
21          'decrypt is deprecated. Use decrypt_xml or decrypt_hash instead.'
22        )
23        decrypt_hash(data)
24      end
25
26      # Decrypts an EncryptedData section of an XML document.
27      #
28      # @param raw_xml [String] the XML document as a string.
29      def decrypt_xml(raw_xml)
30        decrypt_hash(Hash.from_xml(raw_xml))
31      end
32
33      # Decrypts an EncryptedData section of an XML document.
34      #
35      # @param hash [Hash] the XML document converted to a [Hash] using Hash.from_xml.
36      def decrypt_hash(hash)
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        )
43      end
44
45      # Decrypts an EncryptedData Nokogiri::XML::Element.
46      #
47      # @param node [Nokogiri::XML::Element.] the XML node to decrypt.
48      def decrypt_node(node)
49        return node unless !node.nil? && node.name == 'EncryptedData'
50
51        node.parent.replace(decrypt_xml(node.to_s))[0]
52      end
53
54      private
55
56      def symmetric_key_from(encrypted_key, attempts = private_keys.count)
57        cipher, algorithm = cipher_and_algorithm_from(encrypted_key)
58        private_keys.each do |private_key|
59          attempts -= 1
60          return to_plaintext(cipher, private_key, algorithm)
61        rescue OpenSSL::PKey::RSAError
62          raise if attempts.zero?
63        end
64        raise DecryptionError, private_keys
65      end
66
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        ]
76      end
77    end
78  end
79end