rubrik 0.2.1 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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