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 +4 -4
- data/README.md +7 -6
- data/lib/rubrik/document/increment.rb +2 -2
- data/lib/rubrik/document.rb +31 -5
- data/lib/rubrik/fill_signature.rb +3 -6
- data/lib/rubrik/pkcs7_signature.rb +24 -0
- data/lib/rubrik/sign.rb +3 -3
- data/lib/rubrik/version.rb +1 -1
- data/lib/rubrik.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a41daeeefd22b755dd4ae91177d7142e46eff64f5d1877a1c303f4885634f1a2
|
4
|
+
data.tar.gz: d205242211ab51a47a2ac8db5a89267bf90f2e2576450d60311e3de8a5b33d82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
56
|
-
private_key = OpenSSL::PKey::RSA.new(
|
57
|
-
|
58
|
-
|
59
|
-
|
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:,
|
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 << "
|
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"
|
data/lib/rubrik/document.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
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
|
-
|
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:,
|
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 =
|
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
|
-
|
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:,
|
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:,
|
25
|
+
FillSignature.call(output, signature_value_ref:, private_key:, certificate:, certificate_chain:)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
data/lib/rubrik/version.rb
CHANGED
data/lib/rubrik.rb
CHANGED
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.
|
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-
|
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
|