summaryrefslogtreecommitdiff
path: root/lib/xml/kit/decryption.rb
blob: 0a9aa29031447155bb5a39b8f1ca5f378ee480db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# frozen_string_literal: true

module Xml
  module Kit
    # {include:file:spec/xml/kit/decryption_spec.rb}
    class Decryption
      # The list of private keys to use to attempt to decrypt the document.
      attr_reader :cipher_registry, :private_keys

      def initialize(private_keys:, cipher_registry: ::Xml::Kit::Crypto)
        @private_keys = private_keys
        @cipher_registry = cipher_registry
      end

      # Decrypts an EncryptedData section of an XML document.
      #
      # @param data [Hash] the XML document converted to a [Hash] using Hash.from_xml.
      # @deprecated Use {#decrypt_hash} instead of this
      def decrypt(data)
        ::Xml::Kit.deprecate(
          'decrypt is deprecated. Use decrypt_xml or decrypt_hash instead.'
        )
        decrypt_hash(data)
      end

      # Decrypts an EncryptedData section of an XML document.
      #
      # @param raw_xml [String] the XML document as a string.
      def decrypt_xml(raw_xml)
        decrypt_hash(Hash.from_xml(raw_xml))
      end

      # Decrypts an EncryptedData section of an XML document.
      #
      # @param hash [Hash] the XML document converted to a [Hash] using Hash.from_xml.
      def decrypt_hash(hash)
        data = hash['EncryptedData']
        to_plaintext(
          Base64.decode64(data['CipherData']['CipherValue']),
          symmetric_key_from(data['KeyInfo']['EncryptedKey']),
          data['EncryptionMethod']['Algorithm']
        )
      end

      # Decrypts an EncryptedData Nokogiri::XML::Element.
      #
      # @param node [Nokogiri::XML::Element.] the XML node to decrypt.
      def decrypt_node(node)
        return node unless !node.nil? && node.name == 'EncryptedData'

        node.parent.replace(decrypt_xml(node.to_s))[0]
      end

      private

      def symmetric_key_from(encrypted_key, attempts = private_keys.count)
        cipher, algorithm = cipher_and_algorithm_from(encrypted_key)
        private_keys.each do |private_key|
          attempts -= 1
          return to_plaintext(cipher, private_key, algorithm)
        rescue OpenSSL::PKey::RSAError
          raise if attempts.zero?
        end
        raise DecryptionError, private_keys
      end

      def to_plaintext(cipher_text, private_key, algorithm)
        cipher_registry.cipher_for(algorithm, private_key).decrypt(cipher_text)
      end

      def cipher_and_algorithm_from(encrypted_key)
        [
          Base64.decode64(encrypted_key['CipherData']['CipherValue']),
          encrypted_key['EncryptionMethod']['Algorithm']
        ]
      end
    end
  end
end