rubrik 0.1.1 → 0.2.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: b773c74e295ae4696469fb10aa10cbb80705a44b0f7f9e5b7772b08a3bab5493
4
- data.tar.gz: 1d1d6efd3f7d53a649f3369e062bf998ac37234b7e4e4f39c73ddcf48ac4d7f5
3
+ metadata.gz: b9faab13628886a83554fb2dd75b71d7315701da037377bb70d7aba3e9769ffe
4
+ data.tar.gz: 90fbf56ed6afbd1ab7b54ad4290800ad079fd4b600eb4334f2974a248b2631c9
5
5
  SHA512:
6
- metadata.gz: 4a249b176bde1b19d968a846c3eaed6a855d5ad882bc53b9946aed4f5ff902b12c6583f5cec5caa6d8a9086ecf22da910c09832e9ff27e97d262485dff714e2c
7
- data.tar.gz: cfe1ca7af5f35cb43f8cc1660f7c85ea8ca27a9a3ebd19bf1e97cd93e031f8a6c51bb36703c2f88cc4dbf6b8f3e0cb90d6e5c6a4bb7b4542eccdac53094ed49f
6
+ metadata.gz: 3a83c73ca910d0ce84298c65d7f704ed93ccd3104bb44f1d70331b82bdcdecae1355fb2b5a0b8177a9954b3c39394153cbb521cf67ec4d4a677fc98684c90f5e
7
+ data.tar.gz: 0da9fd89c054a40db22642cfb4b390be64fb49c12b42f449d901c898b86bcd10d4f8e72892ecfc59618aca1678f115165209a41497315c3b2f1cde8d29882194
data/README.md CHANGED
@@ -4,13 +4,25 @@ Rubrik is a complete and simple digital signature library that implements the PA
4
4
  Signatures) in pure Ruby. It conforms with PKCS#7 and **will be** compatible with Brazil's AD-RB, AD-RT and EU's B-B
5
5
  and B-T profiles.
6
6
 
7
+ - [Rubrik](#rubrik)
8
+ - [Implementation Status](#implementation-status)
9
+ - [PDF Features](#pdf-features)
10
+ - [Signature Profiles](#signature-profiles)
11
+ - [Installation](#installation)
12
+ - [Usage](#usage)
13
+ - [Development](#development)
14
+ - [References:](#references)
15
+ - [Contributing](#contributing)
16
+ - [License](#license)
17
+ - [Code of Conduct](#code-of-conduct)
18
+
7
19
  ## Implementation Status
8
20
 
9
21
  This gem is under development and may be subjected to breaking changes.
10
22
 
11
23
  ### PDF Features
12
- - [x] Modify PDFs with incremental updates (doesn't modify the documents, only append signature appearence)
13
- - [ ] Signature appearence (stamp)
24
+ - [x] Modify PDFs with incremental updates (doesn't modify the documents, only append signature appearance)
25
+ - [ ] Signature appearance (stamp)
14
26
  - [ ] External (offline) signatures
15
27
 
16
28
  ### Signature Profiles
@@ -63,6 +75,16 @@ After checking out the repo, run `bundle install` to install dependencies. Then,
63
75
 
64
76
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
77
 
78
+ ## References:
79
+ 1. PDF References:
80
+ - [ISO 3200-2](https://pdfa.org/sponsored-standards/)
81
+
82
+ 2. Brazil's technical references:
83
+ - [DOC-ICP-15.03](https://www.gov.br/iti/pt-br/assuntos/legislacao/instrucoes-normativas/IN032021_DOC_15.03_assinada.pdf)
84
+ 3. EU's technical references:
85
+ - [ETSI 319 122-1](https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.02.01_60/en_31912201v010201p.pdf)
86
+ - [ETSI 319 122-2](https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.02.01_60/en_31912201v010201p.pdf)
87
+
66
88
  ## Contributing
67
89
 
68
90
  Bug reports and pull requests are welcome on GitHub at https://github.com/tomascco/rubrik. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/tomascco/rubrik/blob/main/CODE_OF_CONDUCT.md).
@@ -9,19 +9,22 @@ module Rubrik
9
9
  extend T::Sig
10
10
  extend self
11
11
 
12
- sig {params(document: Rubrik::Document, io: T.any(File, Tempfile, StringIO)).returns(T.any(File, Tempfile, StringIO))}
12
+ sig {params(document: Rubrik::Document, io: T.any(File, Tempfile, StringIO)).void}
13
13
  def call(document, io:)
14
14
  document.io.rewind
15
- IO.copy_stream(T.unsafe(document.io), T.unsafe(io))
15
+ IO.copy_stream(document.io, io)
16
16
 
17
17
  io << "\n"
18
- new_xref = Array.new
18
+
19
+ new_xref = T.let([], T::Array[T::Hash[Symbol, Integer]])
20
+ new_xref << {id: 0}
19
21
 
20
22
  document.modified_objects.each do |object|
21
23
  integer_id = T.let(object[:id].to_i, Integer)
22
24
  new_xref << {id: integer_id, offset: io.pos}
23
25
 
24
- io << "#{integer_id} 0 obj\n" "#{serialize(object[:value])}\n" "endobj\n\n"
26
+ value = object[:value]
27
+ io << "#{integer_id} 0 obj\n" "#{SerializeObject[value]}\n" "endobj\n\n"
25
28
  end
26
29
 
27
30
  updated_trailer = document.objects.trailer.dup
@@ -31,72 +34,34 @@ module Rubrik
31
34
  new_xref_pos = io.pos
32
35
 
33
36
  new_xref_subsections = new_xref
34
- .sort_by { _1[:id] }
35
- .chunk_while { _1[:id] + 1 == _2[:id] }
37
+ .sort_by { |entry| entry.fetch(:id) }
38
+ .chunk_while { _1.fetch(:id) + 1 == _2[:id] }
36
39
 
37
40
  io << "xref\n"
38
- io << "0 1\n"
39
- io << "0000000000 65535 f\n"
40
41
 
41
42
  new_xref_subsections.each do |subsection|
42
- starting_id = subsection.first[:id]
43
+ starting_id = T.must(subsection.first).fetch(:id)
43
44
  length = subsection.length
44
45
 
45
46
  io << "#{starting_id} #{length}\n"
47
+
48
+ if starting_id.zero?
49
+ io << "0000000000 65535 f\n"
50
+ subsection.shift
51
+ end
52
+
46
53
  subsection.each { |entry| io << "#{format("%010d", entry[:offset])} 00000 n\n" }
47
54
  end
48
55
 
49
56
  io << "trailer\n"
50
- io << "#{serialize(updated_trailer)}\n"
57
+ io << "#{SerializeObject[updated_trailer]}\n"
51
58
  io << "startxref\n"
52
59
  io << "#{new_xref_pos.to_s}\n"
53
60
  io << "%%EOF\n"
54
-
55
- io.rewind
56
- io
57
61
  end
58
62
 
59
63
  private
60
64
 
61
- sig {params(obj: T.untyped).returns(String)}
62
- def serialize(obj)
63
- case obj
64
- when Hash
65
- serialized_objs = obj.flatten.map { |e| serialize(e) }
66
- "<<#{serialized_objs.join(" ")}>>"
67
- when Symbol
68
- "/#{obj}"
69
- when Array
70
- serialized_objs = obj.map { |e| serialize(e) }
71
- "[#{serialized_objs.join(" ")}]"
72
- when PDF::Reader::Reference
73
- "#{obj.id} #{obj.gen} R"
74
- when String
75
- "(#{obj})"
76
- when TrueClass
77
- "true"
78
- when FalseClass
79
- "false"
80
- when Document::CONTENTS_PLACEHOLDER
81
- "<#{"0" * Document::SIGNATURE_SIZE}>"
82
- when Document::BYTE_RANGE_PLACEHOLDER
83
- "[0 0000000000 0000000000 0000000000]"
84
- when Numeric
85
- obj.to_s
86
- when NilClass
87
- "null"
88
- when PDF::Reader::Stream
89
- <<~OBJECT.chomp
90
- #{serialize(obj.hash)}
91
- stream
92
- #{obj.data}
93
- endstream
94
- OBJECT
95
- else
96
- raise NotImplementedError.new("Don't know how to serialize #{obj}")
97
- end
98
- end
99
-
100
65
  sig {params(document: Rubrik::Document).returns(Integer)}
101
66
  def last_xref_pos(document)
102
67
  PDF::Reader::Buffer.new(document.io, seek: 0).find_first_xref_offset
@@ -0,0 +1,53 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Rubrik
5
+ class Document
6
+ module SerializeObject
7
+ include Kernel
8
+ extend T::Sig
9
+ extend self
10
+
11
+ sig {params(obj: T.untyped).returns(String)}
12
+ def [](obj)
13
+ case obj
14
+ when Hash
15
+ serialized_objs = obj.flatten.map { |e| SerializeObject[e] }
16
+ "<<#{serialized_objs.join(" ")}>>"
17
+ when Symbol
18
+ "/#{obj}"
19
+ when Array
20
+ serialized_objs = obj.map { |e| SerializeObject[e] }
21
+ "[#{serialized_objs.join(" ")}]"
22
+ when PDF::Reader::Reference
23
+ "#{obj.id} #{obj.gen} R"
24
+ when String
25
+ "(#{obj})"
26
+ when TrueClass
27
+ "true"
28
+ when FalseClass
29
+ "false"
30
+ when Document::CONTENTS_PLACEHOLDER
31
+ "<#{"0" * Document::SIGNATURE_SIZE}>"
32
+ when Document::BYTE_RANGE_PLACEHOLDER
33
+ "[0 0000000000 0000000000 0000000000]"
34
+ when Float, Integer
35
+ obj.to_s
36
+ when NilClass
37
+ "null"
38
+ when PDF::Reader::Stream
39
+ <<~OBJECT.chomp
40
+ #{SerializeObject[obj.hash]}
41
+ stream
42
+ #{obj.data}
43
+ endstream
44
+ OBJECT
45
+ else
46
+ raise "Don't know how to serialize #{obj}"
47
+ end
48
+ end
49
+
50
+ alias call []
51
+ end
52
+ end
53
+ end
@@ -12,19 +12,18 @@ module Rubrik
12
12
  SIGNATURE_SIZE = 8_192
13
13
 
14
14
  sig {returns(T.any(File, Tempfile, StringIO))}
15
- attr_reader :io
15
+ attr_accessor :io
16
16
 
17
17
  sig {returns(PDF::Reader::ObjectHash)}
18
- attr_reader :objects
18
+ attr_accessor :objects
19
19
 
20
20
  sig {returns(T::Array[{id: PDF::Reader::Reference, value: T.untyped}])}
21
- attr_reader :modified_objects
22
-
23
- sig {returns(PDF::Reader::Reference)}
24
- attr_reader :interactive_form_id
21
+ attr_accessor :modified_objects
25
22
 
26
23
  sig {returns(Integer)}
27
- attr_reader :last_object_id
24
+ attr_accessor :last_object_id
25
+
26
+ private :io=, :objects=, :modified_objects=, :last_object_id=
28
27
 
29
28
  sig {params(input: T.any(File, Tempfile, StringIO)).void}
30
29
  def initialize(input)
@@ -36,9 +35,11 @@ module Rubrik
36
35
  fetch_or_create_interactive_form!
37
36
  end
38
37
 
39
- sig {void}
38
+ sig {returns(PDF::Reader::Reference)}
39
+ # Returns the reference of the Signature Value dictionary.
40
40
  def add_signature_field
41
- # create signature value dictionary
41
+ # To add an signature to the PDF, we need the following structure
42
+ # Interactive Form -> Signature Field -> Signature Value
42
43
  signature_value_id = assign_new_object_id!
43
44
  modified_objects << {
44
45
  id: signature_value_id,
@@ -63,7 +64,7 @@ module Rubrik
63
64
  V: signature_value_id,
64
65
  Type: :Annot,
65
66
  Subtype: :Widget,
66
- Rect: [20, 20, 120, 120],
67
+ Rect: [0, 0, 0, 0],
67
68
  F: 4,
68
69
  P: first_page_reference
69
70
  }
@@ -75,6 +76,8 @@ module Rubrik
75
76
  modified_objects << {id: first_page_reference, value: modified_page}
76
77
 
77
78
  (interactive_form[:Fields] ||= []) << signature_field_id
79
+
80
+ signature_value_id
78
81
  end
79
82
 
80
83
  private
@@ -87,7 +90,7 @@ module Rubrik
87
90
  sig {void}
88
91
  def fetch_or_create_interactive_form!
89
92
  root_ref = objects.trailer[:Root]
90
- root = T.let(objects.fetch(root_ref), Hash)
93
+ root = T.let(objects.fetch(root_ref), T::Hash[Symbol, T.untyped])
91
94
 
92
95
  if root.key?(:AcroForm)
93
96
  form_id = root[:AcroForm]
@@ -111,17 +114,5 @@ module Rubrik
111
114
  def assign_new_object_id!
112
115
  PDF::Reader::Reference.new(self.last_object_id += 1, 0)
113
116
  end
114
-
115
- sig {params(io: T.any(File, Tempfile, StringIO)).returns(T.any(File, Tempfile, StringIO))}
116
- attr_writer :io
117
-
118
- sig {params(objects: PDF::Reader::ObjectHash).returns(PDF::Reader::ObjectHash)}
119
- attr_writer :objects
120
-
121
- sig {params(modified_objects: T::Array[{id: PDF::Reader::Reference, value: T.untyped}]).returns(T::Array[{id: PDF::Reader::Reference, value: T.untyped}])}
122
- attr_writer :modified_objects
123
-
124
- sig {params(last_object_id: Integer).returns(Integer)}
125
- attr_writer :last_object_id
126
117
  end
127
118
  end
@@ -15,7 +15,7 @@ module Rubrik
15
15
  private_key: OpenSSL::PKey::RSA,
16
16
  public_key: OpenSSL::X509::Certificate,
17
17
  certificate_chain: T::Array[OpenSSL::X509::Certificate])
18
- .returns(T.any(File, StringIO, Tempfile))}
18
+ .void}
19
19
 
20
20
  FIRST_OFFSET = 0
21
21
 
@@ -29,8 +29,7 @@ module Rubrik
29
29
  io.gets("<")
30
30
 
31
31
  first_length = io.pos - 1
32
- # we need to double the SIGNATURE_SIZE because the hex encoding double the data size
33
- # we also need to sum +2 to account for "<" and ">" of the hex string
32
+ # We need to sum +2 to account for "<" and ">" of the hex string
34
33
  second_offset = first_length + Document::SIGNATURE_SIZE + 2
35
34
  second_length = io.size - second_offset
36
35
 
data/lib/rubrik/sign.rb CHANGED
@@ -13,15 +13,15 @@ module Rubrik
13
13
  certificate_chain: T::Array[OpenSSL::X509::Certificate])
14
14
  .void}
15
15
  def self.call(input, output, private_key:, public_key:, certificate_chain: [])
16
+ input.binmode
17
+ output.reopen(T.unsafe(output), "wb+") if !output.is_a?(StringIO)
18
+
16
19
  document = Rubrik::Document.new(input)
17
20
 
18
- document.add_signature_field
21
+ signature_value_ref = document.add_signature_field
19
22
 
20
23
  Document::Increment.call(document, io: output)
21
24
 
22
- signature_value = T.must(document.modified_objects.find { _1.dig(:value, :Type) == :Sig })
23
-
24
- signature_value_ref = T.let(signature_value[:id], PDF::Reader::Reference)
25
25
  FillSignature.call(output, signature_value_ref:, private_key:, public_key:, certificate_chain:)
26
26
  end
27
27
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubrik
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/rubrik.rb CHANGED
@@ -3,7 +3,6 @@
3
3
 
4
4
  require "sorbet-runtime"
5
5
  require "pdf-reader"
6
- require "irb"
7
6
 
8
7
  module Rubrik
9
8
  class Error < StandardError; end
@@ -11,5 +10,6 @@ end
11
10
 
12
11
  require_relative "rubrik/document"
13
12
  require_relative "rubrik/document/increment"
13
+ require_relative "rubrik/document/serialize_object"
14
14
  require_relative "rubrik/fill_signature"
15
15
  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.1.1
4
+ version: 0.2.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-16 00:00:00.000000000 Z
11
+ date: 2023-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pdf-reader
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: openssl
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 2.2.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.2.1
41
55
  description: Sign PDFs digitally in pure Ruby
42
56
  email:
43
57
  - tomascoelho6@gmail.com
@@ -50,6 +64,7 @@ files:
50
64
  - lib/rubrik.rb
51
65
  - lib/rubrik/document.rb
52
66
  - lib/rubrik/document/increment.rb
67
+ - lib/rubrik/document/serialize_object.rb
53
68
  - lib/rubrik/fill_signature.rb
54
69
  - lib/rubrik/sign.rb
55
70
  - lib/rubrik/version.rb
@@ -59,7 +74,7 @@ licenses:
59
74
  metadata:
60
75
  homepage_uri: https://github.com/tomascco/rubrik
61
76
  source_code_uri: https://github.com/tomascco/rubrik
62
- changelog_uri: https://github.com/tomascco/rubrik/blob/main/CHANGELOG.md
77
+ changelog_uri: https://github.com/tomascco/rubrik/releases
63
78
  post_install_message:
64
79
  rdoc_options: []
65
80
  require_paths:
@@ -75,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
90
  - !ruby/object:Gem::Version
76
91
  version: '0'
77
92
  requirements: []
78
- rubygems_version: 3.4.19
93
+ rubygems_version: 3.4.20
79
94
  signing_key:
80
95
  specification_version: 4
81
96
  summary: Sign PDFs digitally in pure Ruby