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
|