Class: Pdfrb::Encryption::PublicKeySecurityHandler
- Inherits:
-
Object
- Object
- Pdfrb::Encryption::PublicKeySecurityHandler
- Defined in:
- lib/pdfrb/encryption/public_key_security_handler.rb
Overview
Public-key security handler (PDF 1.7 §7.6.5, PKCS#7). Each recipient is encoded as a CMS EnvelopedData; the document encryption key is wrapped with the recipient's public key.
Read support: parses /Recipients array elements as PKCS#7 EnvelopedData, attempts decryption with a provided private key and certificate. Write support: builds EnvelopedData for a list of recipient certificates via OpenSSL::PKCS7.encrypt, returning DER bytes suitable for the /Recipients array.
Constant Summary collapse
- DEFAULT_KEY_LENGTH =
AES-256
32
Instance Attribute Summary collapse
-
#certificate ⇒ Object
readonly
Returns the value of attribute certificate.
-
#encrypt_dict ⇒ Object
readonly
Returns the value of attribute encrypt_dict.
-
#file_key ⇒ Object
readonly
Returns the value of attribute file_key.
-
#private_key ⇒ Object
readonly
Returns the value of attribute private_key.
-
#recipients ⇒ Object
readonly
Returns the value of attribute recipients.
Class Method Summary collapse
-
.build_recipients(file_key:, recipient_certs:, cipher: "AES-256-CBC") ⇒ Object
Build /Recipients array of DER EnvelopedData bytes for each recipient cert.
-
.for_writer(recipient_certs:, cipher: "AES-256-CBC", key_length: DEFAULT_KEY_LENGTH) ⇒ Object
Construct a write-side handler.
Instance Method Summary collapse
-
#can_decrypt? ⇒ Boolean
Whether this handler can attempt decryption: needs a private key AND a matching certificate AND at least one recipient envelope.
- #decrypt_data(data, oid, gen) ⇒ Object
-
#encrypt_data(data, oid, gen) ⇒ Object
(also: #encrypt)
Per-object encryption: AES-256-CBC with IV prefix.
-
#initialize(encrypt_dict: nil, recipients: [], private_key: nil, certificate: nil, file_key: nil) ⇒ PublicKeySecurityHandler
constructor
A new instance of PublicKeySecurityHandler.
-
#unwrap_key ⇒ Object
Attempt to unwrap the document encryption key using the private key.
Constructor Details
#initialize(encrypt_dict: nil, recipients: [], private_key: nil, certificate: nil, file_key: nil) ⇒ PublicKeySecurityHandler
Returns a new instance of PublicKeySecurityHandler.
29 30 31 32 33 34 35 36 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 29 def initialize(encrypt_dict: nil, recipients: [], private_key: nil, certificate: nil, file_key: nil) @encrypt_dict = encrypt_dict @recipients = recipients @private_key = private_key @certificate = certificate @file_key = file_key end |
Instance Attribute Details
#certificate ⇒ Object (readonly)
Returns the value of attribute certificate.
20 21 22 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 20 def certificate @certificate end |
#encrypt_dict ⇒ Object (readonly)
Returns the value of attribute encrypt_dict.
20 21 22 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 20 def encrypt_dict @encrypt_dict end |
#file_key ⇒ Object (readonly)
Returns the value of attribute file_key.
20 21 22 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 20 def file_key @file_key end |
#private_key ⇒ Object (readonly)
Returns the value of attribute private_key.
20 21 22 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 20 def private_key @private_key end |
#recipients ⇒ Object (readonly)
Returns the value of attribute recipients.
20 21 22 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 20 def recipients @recipients end |
Class Method Details
.build_recipients(file_key:, recipient_certs:, cipher: "AES-256-CBC") ⇒ Object
Build /Recipients array of DER EnvelopedData bytes for each recipient cert. The file_key is wrapped into each envelope. Returns the Array of DER strings.
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 62 def self.build_recipients(file_key:, recipient_certs:, cipher: "AES-256-CBC") recipient_certs.map do |cert| pkcs7 = OpenSSL::PKCS7.encrypt( Array(cert), file_key, OpenSSL::Cipher.new(cipher), OpenSSL::PKCS7::BINARY ) pkcs7.to_der end end |
.for_writer(recipient_certs:, cipher: "AES-256-CBC", key_length: DEFAULT_KEY_LENGTH) ⇒ Object
Construct a write-side handler. Generates a random DEFAULT_KEY_LENGTH-byte file_key, wraps it for each recipient cert, returns a handler whose encrypt_data is ready to use.
77 78 79 80 81 82 83 84 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 77 def self.for_writer(recipient_certs:, cipher: "AES-256-CBC", key_length: DEFAULT_KEY_LENGTH) file_key = SecureRandom.random_bytes(key_length) recipients = build_recipients(file_key: file_key, recipient_certs: recipient_certs, cipher: cipher) new(recipients: recipients, file_key: file_key) end |
Instance Method Details
#can_decrypt? ⇒ Boolean
Whether this handler can attempt decryption: needs a private key AND a matching certificate AND at least one recipient envelope.
41 42 43 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 41 def can_decrypt? !private_key.nil? && !certificate.nil? && recipients.any? end |
#decrypt_data(data, oid, gen) ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 101 def decrypt_data(data, oid, gen) key = unwrap_key return data unless key return data if data.bytesize < 16 obj_key = per_object_key(oid, gen, key) iv = data.byteslice(0, 16) ciphertext = data.byteslice(16..) decipher = OpenSSL::Cipher.new("AES-256-CBC") decipher.decrypt decipher.key = obj_key decipher.iv = iv decipher.update(ciphertext) + decipher.final rescue OpenSSL::Cipher::CipherError data end |
#encrypt_data(data, oid, gen) ⇒ Object Also known as: encrypt
Per-object encryption: AES-256-CBC with IV prefix. The per-object key is SHA-256(file_key ‖ oid ‖ gen).
88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 88 def encrypt_data(data, oid, gen) return data unless @file_key obj_key = per_object_key(oid, gen) iv = SecureRandom.random_bytes(16) cipher = OpenSSL::Cipher.new("AES-256-CBC") cipher.encrypt cipher.key = obj_key cipher.iv = iv encrypted = cipher.update(data) + cipher.final iv + encrypted end |
#unwrap_key ⇒ Object
Attempt to unwrap the document encryption key using the private key. Returns the raw key bytes, or nil if the key couldn't be extracted.
48 49 50 51 52 53 54 55 56 57 |
# File 'lib/pdfrb/encryption/public_key_security_handler.rb', line 48 def unwrap_key return @file_key if @file_key return nil unless can_decrypt? recipients.each do |envelope_bytes| key = try_unwrap_envelope(envelope_bytes) return key if key end nil end |