rubrik 0.2.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9faab13628886a83554fb2dd75b71d7315701da037377bb70d7aba3e9769ffe
4
- data.tar.gz: 90fbf56ed6afbd1ab7b54ad4290800ad079fd4b600eb4334f2974a248b2631c9
3
+ metadata.gz: a41daeeefd22b755dd4ae91177d7142e46eff64f5d1877a1c303f4885634f1a2
4
+ data.tar.gz: d205242211ab51a47a2ac8db5a89267bf90f2e2576450d60311e3de8a5b33d82
5
5
  SHA512:
6
- metadata.gz: 3a83c73ca910d0ce84298c65d7f704ed93ccd3104bb44f1d70331b82bdcdecae1355fb2b5a0b8177a9954b3c39394153cbb521cf67ec4d4a677fc98684c90f5e
7
- data.tar.gz: 0da9fd89c054a40db22642cfb4b390be64fb49c12b42f449d901c898b86bcd10d4f8e72892ecfc59618aca1678f115165209a41497315c3b2f1cde8d29882194
6
+ metadata.gz: 4e73a024012af2d658489a007a449ed05270c02ccbaf4fa9c49930827e98aeb4bc1ea71f70e1c923e7d5fafe17e334f27b5ae66a4e3f49959f34ea8e6836ec58
7
+ data.tar.gz: 23a4ca82df06a937feb613b5320351d44067c0a7db596ccc854486256609856f97361011be725cf987ede6fd7e6e9a2d4c7249ba8683ef010b5718e302fa0e8d
data/README.md CHANGED
@@ -22,6 +22,7 @@ This gem is under development and may be subjected to breaking changes.
22
22
 
23
23
  ### PDF Features
24
24
  - [x] Modify PDFs with incremental updates (doesn't modify the documents, only append signature appearance)
25
+ - [ ] Encryption Support
25
26
  - [ ] Signature appearance (stamp)
26
27
  - [ ] External (offline) signatures
27
28
 
@@ -52,14 +53,14 @@ input_pdf = File.open("example.pdf", "rb")
52
53
  output_pdf = File.open("signed_example.pdf", "wb+") # needs read permission
53
54
 
54
55
  # Load Certificate(s)
55
- certificate = File.open("example_cert.pem", "rb")
56
- private_key = OpenSSL::PKey::RSA.new(certificate, "")
57
- certificate.rewind
58
- public_key = OpenSSL::X509::Certificate.new(certificate)
59
- certificate.close
56
+ certificate_file = File.open("example_cert.pem", "rb")
57
+ private_key = OpenSSL::PKey::RSA.new(certificate_file, "")
58
+ certificate_file.rewind
59
+ certificate = OpenSSL::X509::Certificate.new(certificate_file)
60
+ certificate_file.close
60
61
 
61
62
  # Will write the signed document to `output_pdf`
62
- Rubrik::Sign.call(input_pdf, output_pdf, private_key:, public_key:, certificate_chain: [])
63
+ Rubrik::Sign.call(input_pdf, output_pdf, private_key:, certificate:, certificate_chain: [])
63
64
 
64
65
  # Don't forget to close the files
65
66
  input_pdf.close
@@ -46,11 +46,11 @@ module Rubrik
46
46
  io << "#{starting_id} #{length}\n"
47
47
 
48
48
  if starting_id.zero?
49
- io << "0000000000 65535 f\n"
49
+ io << "#{format("%010d", document.first_free_object_id)} 65535 f \n"
50
50
  subsection.shift
51
51
  end
52
52
 
53
- subsection.each { |entry| io << "#{format("%010d", entry[:offset])} 00000 n\n" }
53
+ subsection.each { |entry| io << "#{format("%010d", entry[:offset])} 00000 n \n" }
54
54
  end
55
55
 
56
56
  io << "trailer\n"
@@ -17,19 +17,25 @@ module Rubrik
17
17
  sig {returns(PDF::Reader::ObjectHash)}
18
18
  attr_accessor :objects
19
19
 
20
+ sig {returns(Integer)}
21
+ attr_accessor :first_free_object_id
22
+
20
23
  sig {returns(T::Array[{id: PDF::Reader::Reference, value: T.untyped}])}
21
24
  attr_accessor :modified_objects
22
25
 
23
26
  sig {returns(Integer)}
24
27
  attr_accessor :last_object_id
25
28
 
26
- private :io=, :objects=, :modified_objects=, :last_object_id=
29
+ private :io=, :objects=, :first_free_object_id=, :modified_objects=, :last_object_id=
27
30
 
28
31
  sig {params(input: T.any(File, Tempfile, StringIO)).void}
29
32
  def initialize(input)
30
33
  self.io = input
31
34
  self.objects = PDF::Reader::ObjectHash.new(input)
32
- self.last_object_id = objects.size
35
+
36
+ self.last_object_id = objects.trailer[:Size] - 1
37
+ self.first_free_object_id = find_first_free_object_id
38
+
33
39
  self.modified_objects = []
34
40
 
35
41
  fetch_or_create_interactive_form!
@@ -70,10 +76,20 @@ module Rubrik
70
76
  }
71
77
  }
72
78
 
73
- modified_page = objects.fetch(first_page_reference).dup
74
- (modified_page[:Annots] ||= []) << signature_field_id
79
+ first_page = objects.fetch(first_page_reference)
80
+ annots = first_page[:Annots]
81
+
82
+ if annots.is_a?(PDF::Reader::Reference)
83
+ new_annots = objects.fetch(annots).dup
84
+ new_annots << signature_field_id
85
+
86
+ modified_objects << {id: annots, value: new_annots}
87
+ else
88
+ new_first_page = first_page.dup
89
+ (new_first_page[:Annots] ||= []) << signature_field_id
75
90
 
76
- modified_objects << {id: first_page_reference, value: modified_page}
91
+ modified_objects << {id: first_page_reference, value: new_first_page}
92
+ end
77
93
 
78
94
  (interactive_form[:Fields] ||= []) << signature_field_id
79
95
 
@@ -87,6 +103,16 @@ module Rubrik
87
103
  T.must(modified_objects.first).fetch(:value)
88
104
  end
89
105
 
106
+ sig{returns(Integer)}
107
+ def find_first_free_object_id
108
+ return 0 if last_object_id == objects.size
109
+
110
+ xref = objects.send(:xref).instance_variable_get(:@xref)
111
+ missing_ids = (1..last_object_id).to_a - xref.keys
112
+
113
+ T.must(missing_ids.min)
114
+ end
115
+
90
116
  sig {void}
91
117
  def fetch_or_create_interactive_form!
92
118
  root_ref = objects.trailer[:Root]
@@ -1,8 +1,6 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "openssl"
5
-
6
4
  module Rubrik
7
5
  module FillSignature
8
6
  extend T::Sig
@@ -13,13 +11,13 @@ module Rubrik
13
11
  io: T.any(File, StringIO, Tempfile),
14
12
  signature_value_ref: PDF::Reader::Reference,
15
13
  private_key: OpenSSL::PKey::RSA,
16
- public_key: OpenSSL::X509::Certificate,
14
+ certificate: OpenSSL::X509::Certificate,
17
15
  certificate_chain: T::Array[OpenSSL::X509::Certificate])
18
16
  .void}
19
17
 
20
18
  FIRST_OFFSET = 0
21
19
 
22
- def call(io, signature_value_ref:, private_key:, public_key:, certificate_chain: [])
20
+ def call(io, signature_value_ref:, private_key:, certificate:, certificate_chain: [])
23
21
  io.rewind
24
22
 
25
23
  signature_value_offset = PDF::Reader::XRef.new(io)[signature_value_ref]
@@ -55,8 +53,7 @@ module Rubrik
55
53
  io.pos = second_offset
56
54
  data_to_sign += T.must(io.read(second_length))
57
55
 
58
- signature = OpenSSL::PKCS7.sign(public_key, private_key, data_to_sign, certificate_chain,
59
- OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
56
+ signature = PKCS7Signature.call(data_to_sign, private_key:, certificate:)
60
57
  hex_signature = T.let(signature, String).unpack1("H*")
61
58
 
62
59
  padded_contents_field = "<#{hex_signature.ljust(Document::SIGNATURE_SIZE, "0")}>"
@@ -0,0 +1,24 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "openssl"
5
+
6
+ module Rubrik
7
+ module PKCS7Signature
8
+ extend T::Sig
9
+ extend self
10
+
11
+ OPEN_SSL_FLAGS = OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY
12
+
13
+ sig {params(
14
+ data: String,
15
+ private_key: OpenSSL::PKey::RSA,
16
+ certificate: OpenSSL::X509::Certificate,
17
+ certificate_chain: T::Array[OpenSSL::X509::Certificate]
18
+ ).returns(String)
19
+ }
20
+ def call(data, private_key:, certificate:, certificate_chain: [])
21
+ OpenSSL::PKCS7.sign(certificate, private_key, data, certificate_chain, OPEN_SSL_FLAGS).to_der
22
+ end
23
+ end
24
+ end
data/lib/rubrik/sign.rb CHANGED
@@ -9,10 +9,10 @@ module Rubrik
9
9
  input: T.any(File, Tempfile, StringIO),
10
10
  output: T.any(File, Tempfile, StringIO),
11
11
  private_key: OpenSSL::PKey::RSA,
12
- public_key: OpenSSL::X509::Certificate,
12
+ certificate: OpenSSL::X509::Certificate,
13
13
  certificate_chain: T::Array[OpenSSL::X509::Certificate])
14
14
  .void}
15
- def self.call(input, output, private_key:, public_key:, certificate_chain: [])
15
+ def self.call(input, output, private_key:, certificate:, certificate_chain: [])
16
16
  input.binmode
17
17
  output.reopen(T.unsafe(output), "wb+") if !output.is_a?(StringIO)
18
18
 
@@ -22,7 +22,7 @@ module Rubrik
22
22
 
23
23
  Document::Increment.call(document, io: output)
24
24
 
25
- FillSignature.call(output, signature_value_ref:, private_key:, public_key:, certificate_chain:)
25
+ FillSignature.call(output, signature_value_ref:, private_key:, certificate:, certificate_chain:)
26
26
  end
27
27
  end
28
28
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubrik
4
- VERSION = "0.2.1"
4
+ VERSION = "1.0.1"
5
5
  end
data/lib/rubrik.rb CHANGED
@@ -12,4 +12,5 @@ require_relative "rubrik/document"
12
12
  require_relative "rubrik/document/increment"
13
13
  require_relative "rubrik/document/serialize_object"
14
14
  require_relative "rubrik/fill_signature"
15
+ require_relative "rubrik/pkcs7_signature"
15
16
  require_relative "rubrik/sign"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubrik
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomás Coêlho
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-20 00:00:00.000000000 Z
11
+ date: 2023-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pdf-reader
@@ -66,6 +66,7 @@ files:
66
66
  - lib/rubrik/document/increment.rb
67
67
  - lib/rubrik/document/serialize_object.rb
68
68
  - lib/rubrik/fill_signature.rb
69
+ - lib/rubrik/pkcs7_signature.rb
69
70
  - lib/rubrik/sign.rb
70
71
  - lib/rubrik/version.rb
71
72
  homepage: https://github.com/tomascco/rubrik