File: security_handler_factory.rb

package info (click to toggle)
ruby-pdf-reader 2.15.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 33,512 kB
  • sloc: ruby: 11,959; sh: 46; makefile: 11
file content (92 lines) | stat: -rw-r--r-- 3,299 bytes parent folder | download
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
80
81
82
83
84
85
86
87
88
89
90
91
92
# coding: utf-8
# typed: strict
# frozen_string_literal: true

class PDF::Reader
  # Examines the Encrypt entry of a PDF trailer (if any) and returns an object that's
  # able to decrypt the file.
  class SecurityHandlerFactory

    #: (Hash[Symbol, untyped], Array[untyped] | nil, String | nil) -> (
    #|   NullSecurityHandler |
    #|   AesV2SecurityHandler |
    #|   Rc4SecurityHandler |
    #|   AesV3SecurityHandler |
    #|   UnimplementedSecurityHandler
    #| )
    def self.build(encrypt, doc_id, password)
      doc_id   ||= []
      password ||= ""

      if encrypt.nil?
        NullSecurityHandler.new
      elsif standard?(encrypt)
        build_standard_handler(encrypt, doc_id, password)
      elsif standard_v5?(encrypt)
        build_v5_handler(encrypt, doc_id, password)
      else
        UnimplementedSecurityHandler.new
      end
    end

    #: (Hash[Symbol, untyped], Array[untyped], String) -> (
    #|    AesV2SecurityHandler | Rc4SecurityHandler
    #| )
    def self.build_standard_handler(encrypt, doc_id, password)
      encmeta = !encrypt.has_key?(:EncryptMetadata) || encrypt[:EncryptMetadata].to_s == "true"
      key_builder = StandardKeyBuilder.new(
        key_length: (encrypt[:Length] || 40).to_i,
        revision: encrypt[:R],
        owner_key: encrypt[:O],
        user_key: encrypt[:U],
        permissions: encrypt[:P].to_i,
        encrypted_metadata: encmeta,
        file_id: doc_id.first,
      )
      cfm = encrypt.fetch(:CF, {}).fetch(encrypt[:StmF], {}).fetch(:CFM, nil)
      if cfm == :AESV2
        AesV2SecurityHandler.new(key_builder.key(password))
      else
        Rc4SecurityHandler.new(key_builder.key(password))
      end
    end

    #: (Hash[Symbol, untyped], Array[untyped], String) -> (AesV3SecurityHandler)
    def self.build_v5_handler(encrypt, doc_id, password)
      key_builder = KeyBuilderV5.new(
        owner_key: encrypt[:O],
        user_key: encrypt[:U],
        owner_encryption_key: encrypt[:OE],
        user_encryption_key: encrypt[:UE],
      )
      AesV3SecurityHandler.new(key_builder.key(password))
    end

    # This handler supports all encryption that follows upto PDF 1.5 spec (revision 4)
    #: (Hash[Symbol, untyped]) -> bool
    def self.standard?(encrypt)
      return false if encrypt.nil?

      filter = encrypt.fetch(:Filter, :Standard)
      version = encrypt.fetch(:V, 0)
      algorithm = encrypt.fetch(:CF, {}).fetch(encrypt[:StmF], {}).fetch(:CFM, nil)
      (filter == :Standard) && (encrypt[:StmF] == encrypt[:StrF]) &&
        (version <= 3 || (version == 4 && ((algorithm == :V2) || (algorithm == :AESV2))))
    end

    # This handler supports both
    # - AES-256 encryption defined in PDF 1.7 Extension Level 3 ('revision 5')
    # - AES-256 encryption defined in PDF 2.0 ('revision 6')
    #: (Hash[Symbol, untyped]) -> untyped
    def self.standard_v5?(encrypt)
      return false if encrypt.nil?

      filter = encrypt.fetch(:Filter, :Standard)
      version = encrypt.fetch(:V, 0)
      revision = encrypt.fetch(:R, 0)
      algorithm = encrypt.fetch(:CF, {}).fetch(encrypt[:StmF], {}).fetch(:CFM, nil)
      (filter == :Standard) && (encrypt[:StmF] == encrypt[:StrF]) &&
          ((version == 5) && (revision == 5 || revision == 6) && (algorithm == :AESV3))
    end
  end
end