origami-docspring 2.3.1 → 2.3.2
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/CHANGELOG.md +10 -0
- data/README.md +10 -6
- data/lib/origami/signature.rb +50 -31
- data/lib/origami/version.rb +1 -1
- data/test/test_pdf_sign.rb +49 -7
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2404e2f61e089ef30323cb132ac9af123f99124c28505a25b148d0d59d0d49a
|
4
|
+
data.tar.gz: 89f0eb165da4b1d762ca102fc52d74681346026e6093067705725346e9df18f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8352a4ee288b72d720734c817ed2fe7c31a99cbaf383c50e3545bf4585d02d15f8c4d5b47a69cc8017c73a7f6a20696ad8d5ea9a59373bcdffb89bbdd76ade7b
|
7
|
+
data.tar.gz: 8c17549cef4f4a533541a2b266b6df73e8a142b1fb50e2f10fe2850a955ddf840d787bdb643dfa5f720bf70e61e43f2c34dd2574b153195af5b9079ca6fae79e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
2.3.2 (2025-03-25)
|
2
|
+
-----
|
3
|
+
* Added support for verifying multiple signatures in PDFs
|
4
|
+
* Only validate ByteRange for the last signature in multi-signature files
|
5
|
+
* Added new `signatures` method to get all signatures in a PDF
|
6
|
+
|
7
|
+
2.3.1 (2025-03-25)
|
8
|
+
-----
|
9
|
+
* Loosened runtime dependencies, removed specific version constraints
|
10
|
+
|
1
11
|
2.3.0 (2025-03-25)
|
2
12
|
-----
|
3
13
|
* Updated to support Ruby 3.2, 3.3, and 3.4
|
data/README.md
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
Origami
|
2
2
|
=====
|
3
|
-
|
4
|
-
[
|
5
|
-
|
3
|
+
|
4
|
+
> **Note**: This is a maintained fork of the original Origami library, released as the gem [origami-docspring](https://rubygems.org/gems/origami-docspring) on RubyGems. Despite the name change, you still `require 'origami'` and use the `Origami` module in your code.
|
5
|
+
|
6
|
+
[](https://rubygems.org/gems/origami-docspring)
|
7
|
+
[](https://github.com/DocSpring/origami-docspring/actions/workflows/ci.yml)
|
8
|
+
[](https://rubygems.org/gems/origami-docspring)
|
6
9
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
7
10
|
|
8
11
|
Overview
|
@@ -27,7 +30,7 @@ Origami is able to parse PDF, FDF and PPKLite (Adobe certificate store) files.
|
|
27
30
|
Requirements
|
28
31
|
------------
|
29
32
|
|
30
|
-
As of version 2, the minimal version required to run
|
33
|
+
As of version 2.3.0, the minimal version required to run origami-docspring is Ruby 3.2.
|
31
34
|
|
32
35
|
Some optional features require additional gems:
|
33
36
|
|
@@ -36,9 +39,9 @@ Some optional features require additional gems:
|
|
36
39
|
Quick start
|
37
40
|
-----------
|
38
41
|
|
39
|
-
First install
|
42
|
+
First install the gem:
|
40
43
|
|
41
|
-
$ gem install origami
|
44
|
+
$ gem install origami-docspring
|
42
45
|
|
43
46
|
Then import Origami with:
|
44
47
|
|
@@ -109,6 +112,7 @@ License
|
|
109
112
|
Origami is distributed under the [LGPL](COPYING.LESSER) license.
|
110
113
|
|
111
114
|
Copyright © 2019 Guillaume Delugré.
|
115
|
+
Copyright © 2025 DocSpring, Inc.
|
112
116
|
|
113
117
|
[the-ruby-racer]: https://rubygems.org/gems/therubyracer
|
114
118
|
[pdfwalker-gem]: https://rubygems.org/gems/pdfwalker
|
data/lib/origami/signature.rb
CHANGED
@@ -38,31 +38,34 @@ module Origami
|
|
38
38
|
use_system_store: false,
|
39
39
|
allow_self_signed: false,
|
40
40
|
&verify_cb)
|
41
|
-
digsig
|
42
|
-
|
41
|
+
signatures.each_with_index do |digsig, index|
|
42
|
+
digsig = digsig.cast_to(Signature::DigitalSignature) unless digsig.is_a?(Signature::DigitalSignature)
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
signature = digsig.signature_data
|
45
|
+
chain = digsig.certificate_chain
|
46
|
+
subfilter = digsig.SubFilter.value
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
store = OpenSSL::X509::Store.new
|
49
|
+
store.set_default_paths if use_system_store
|
50
|
+
trusted_certs.each { |ca| store.add_cert(ca) }
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
store.verify_callback = ->(success, ctx) {
|
53
|
+
return true if success
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
error = ctx.error
|
56
|
+
is_self_signed = error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
|
57
|
+
error == OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN
|
58
58
|
|
59
|
-
|
59
|
+
return true if is_self_signed && allow_self_signed && verify_cb.nil?
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
verify_cb.call(ctx) unless verify_cb.nil?
|
62
|
+
}
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
data = (index == signatures.length - 1) ? extract_signed_data(digsig, last_sig: true) : extract_signed_data(digsig)
|
65
|
+
return false unless Signature.verify(subfilter.to_s, data, signature, store, chain)
|
66
|
+
end
|
67
|
+
|
68
|
+
true
|
66
69
|
end
|
67
70
|
|
68
71
|
#
|
@@ -300,28 +303,44 @@ module Origami
|
|
300
303
|
raise SignatureError, "Cannot find digital signature"
|
301
304
|
end
|
302
305
|
|
306
|
+
def signatures
|
307
|
+
raise SignatureError, "Not a signed document" unless signed?
|
308
|
+
|
309
|
+
dig_sigs = []
|
310
|
+
each_field do |field|
|
311
|
+
dig_sigs << field.V if field.FT == :Sig && field.V.is_a?(Dictionary)
|
312
|
+
end
|
313
|
+
|
314
|
+
return dig_sigs if dig_sigs.count > 0
|
315
|
+
raise SignatureError, "Cannot find digital signature"
|
316
|
+
end
|
317
|
+
|
303
318
|
private
|
304
319
|
|
305
320
|
#
|
306
321
|
# Verifies the ByteRange field of a digital signature and returned the signed data.
|
322
|
+
# Only check for valid ByteRange when it is the last signature
|
307
323
|
#
|
308
|
-
def extract_signed_data(digsig)
|
324
|
+
def extract_signed_data(digsig, last_sig: false)
|
309
325
|
# Computes the boundaries of the Contents field.
|
310
|
-
|
326
|
+
r1, r2 = digsig.ranges
|
311
327
|
|
312
|
-
|
313
|
-
|
314
|
-
Object.typeof(stream).parse(stream)
|
315
|
-
end_sig = stream.pos
|
316
|
-
stream.terminate
|
328
|
+
if last_sig
|
329
|
+
start_sig = digsig[:Contents].file_offset
|
317
330
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
331
|
+
stream = StringScanner.new(original_data)
|
332
|
+
stream.pos = digsig[:Contents].file_offset
|
333
|
+
Object.typeof(stream).parse(stream)
|
334
|
+
end_sig = stream.pos
|
335
|
+
stream.terminate
|
336
|
+
|
337
|
+
if (r1.begin != 0) ||
|
338
|
+
(r2.end != original_data.size) ||
|
339
|
+
(r1.end != start_sig) ||
|
340
|
+
(r2.begin != end_sig)
|
323
341
|
|
324
|
-
|
342
|
+
raise SignatureError, "Invalid signature byte range"
|
343
|
+
end
|
325
344
|
end
|
326
345
|
|
327
346
|
original_data[r1] + original_data[r2]
|
data/lib/origami/version.rb
CHANGED
data/test/test_pdf_sign.rb
CHANGED
@@ -3,8 +3,11 @@
|
|
3
3
|
require 'minitest/autorun'
|
4
4
|
require 'stringio'
|
5
5
|
require 'openssl'
|
6
|
+
require 'origami'
|
6
7
|
|
7
8
|
class TestSign < Minitest::Test
|
9
|
+
include Origami
|
10
|
+
|
8
11
|
def create_self_signed_ca_certificate(key_size, expires)
|
9
12
|
key = OpenSSL::PKey::RSA.new key_size
|
10
13
|
|
@@ -55,13 +58,7 @@ class TestSign < Minitest::Test
|
|
55
58
|
def sign_document_with_method(method)
|
56
59
|
document, annotation = setup_document_with_annotation
|
57
60
|
|
58
|
-
|
59
|
-
method: method,
|
60
|
-
annotation: annotation,
|
61
|
-
issuer: "Guillaume Delugré",
|
62
|
-
location: "France",
|
63
|
-
contact: "origami@localhost",
|
64
|
-
reason: "Example")
|
61
|
+
sign_document(annotation, document, method)
|
65
62
|
|
66
63
|
assert document.frozen?
|
67
64
|
assert document.signed?
|
@@ -83,6 +80,27 @@ class TestSign < Minitest::Test
|
|
83
80
|
assert result
|
84
81
|
end
|
85
82
|
|
83
|
+
def sign_document_twice_with_method(method)
|
84
|
+
document, annotation = setup_document_with_annotation
|
85
|
+
|
86
|
+
2.times do
|
87
|
+
sign_document(annotation, document, method)
|
88
|
+
end
|
89
|
+
|
90
|
+
assert document.frozen?
|
91
|
+
assert document.signed?
|
92
|
+
|
93
|
+
output = StringIO.new
|
94
|
+
document.save(output)
|
95
|
+
|
96
|
+
document = PDF.read(output.reopen(output.string, 'r'), verbosity: Parser::VERBOSE_QUIET)
|
97
|
+
|
98
|
+
refute document.verify
|
99
|
+
assert document.verify(allow_self_signed: true)
|
100
|
+
assert document.verify(trusted_certs: [@cert])
|
101
|
+
refute document.verify(trusted_certs: [@other_cert])
|
102
|
+
end
|
103
|
+
|
86
104
|
def test_sign_pkcs7_sha1
|
87
105
|
sign_document_with_method(Signature::PKCS7_SHA1)
|
88
106
|
end
|
@@ -94,4 +112,28 @@ class TestSign < Minitest::Test
|
|
94
112
|
def test_sign_x509_sha1
|
95
113
|
sign_document_with_method(Signature::PKCS1_RSA_SHA1)
|
96
114
|
end
|
115
|
+
|
116
|
+
def test_sign_pkcs7_sha1_twice
|
117
|
+
sign_document_twice_with_method(Signature::PKCS7_SHA1)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_sign_pkcs7_detached_twice
|
121
|
+
sign_document_twice_with_method(Signature::PKCS7_DETACHED)
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_sign_x509_sha1_twice
|
125
|
+
sign_document_twice_with_method(Signature::PKCS1_RSA_SHA1)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def sign_document(annotation, document, method)
|
131
|
+
document.sign(@cert, @key,
|
132
|
+
method: method,
|
133
|
+
annotation: annotation,
|
134
|
+
issuer: "Guillaume Delugré",
|
135
|
+
location: "France",
|
136
|
+
contact: "origami@localhost",
|
137
|
+
reason: "Example")
|
138
|
+
end
|
97
139
|
end
|