easy_code_sign 0.1.0 → 0.2.0

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.
@@ -2,12 +2,12 @@
2
2
 
3
3
  require "test_helper"
4
4
  require "tempfile"
5
- require "hexapdf"
5
+ require "openssl"
6
6
 
7
7
  class PdfFileSignableTest < EasyCodeSignTest
8
8
  def setup
9
9
  @temp_pdf = Tempfile.new(["test", ".pdf"])
10
- create_test_pdf(@temp_pdf.path)
10
+ create_minimal_pdf(@temp_pdf.path)
11
11
  end
12
12
 
13
13
  def teardown
@@ -49,77 +49,97 @@ class PdfFileSignableTest < EasyCodeSignTest
49
49
  refute signable.signed?
50
50
  end
51
51
 
52
- def test_extract_signature_returns_nil_for_unsigned
53
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
54
- assert_nil signable.extract_signature
55
- end
56
-
57
- def test_prepare_for_signing_succeeds
58
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
59
- signable.prepare_for_signing
60
-
61
- assert_equal "PDF_SIGNING_PLACEHOLDER", signable.content_to_sign
62
- end
52
+ def test_apply_signature_produces_signed_pdf
53
+ key = generate_rsa_key
54
+ cert = build_self_signed_cert(key)
55
+ out = Tempfile.new(["signed", ".pdf"])
63
56
 
64
- def test_default_signature_config
65
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
66
-
67
- refute signable.signature_config[:visible]
68
- assert_equal 1, signable.signature_config[:page]
69
- assert_equal :bottom_right, signable.signature_config[:position]
70
- end
71
-
72
- def test_visible_signature_option
73
57
  signable = EasyCodeSign::Signable::PdfFile.new(
74
58
  @temp_pdf.path,
75
- visible_signature: true,
76
- signature_position: :top_left
59
+ output_path: out.path,
60
+ signature_reason: "Testing"
61
+ )
62
+
63
+ signed_path = signable.apply_signature(
64
+ ->(hash) { key.sign_raw("SHA256", hash) },
65
+ [cert]
77
66
  )
78
67
 
79
- assert signable.signature_config[:visible]
80
- assert_equal :top_left, signable.signature_config[:position]
68
+ assert File.exist?(signed_path)
69
+
70
+ verifier = EasyCodeSign::Signable::PdfFile.new(signed_path)
71
+ sig = verifier.extract_signature
72
+
73
+ assert sig, "Expected a signature to be present"
74
+ assert sig[:contents], "Expected /Contents to be non-empty"
75
+ assert_equal 4, sig[:byte_range].size
76
+ ensure
77
+ out.close
78
+ out.unlink
81
79
  end
82
80
 
83
- def test_signature_reason_and_location
84
- signable = EasyCodeSign::Signable::PdfFile.new(
85
- @temp_pdf.path,
86
- signature_reason: "Approved",
87
- signature_location: "New York"
88
- )
81
+ def test_signed_returns_true_after_signing
82
+ key = generate_rsa_key
83
+ cert = build_self_signed_cert(key)
84
+ out = Tempfile.new(["signed2", ".pdf"])
85
+
86
+ signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path, output_path: out.path)
87
+ signable.apply_signature(->(hash) { key.sign_raw("SHA256", hash) }, [cert])
89
88
 
90
- assert_equal "Approved", signable.signature_config[:reason]
91
- assert_equal "New York", signable.signature_config[:location]
89
+ verifier = EasyCodeSign::Signable::PdfFile.new(out.path)
90
+ assert verifier.signed?
91
+ ensure
92
+ out.close
93
+ out.unlink
92
94
  end
93
95
 
94
- def test_signature_page_option
95
- signable = EasyCodeSign::Signable::PdfFile.new(
96
- @temp_pdf.path,
97
- signature_page: 2
98
- )
96
+ private
99
97
 
100
- assert_equal 2, signable.signature_config[:page]
98
+ def generate_rsa_key
99
+ OpenSSL::PKey::RSA.generate(2048)
101
100
  end
102
101
 
103
- def test_signable_for_returns_pdf_file
104
- signable = EasyCodeSign.signable_for(@temp_pdf.path)
105
- assert_instance_of EasyCodeSign::Signable::PdfFile, signable
102
+ def build_self_signed_cert(key)
103
+ cert = OpenSSL::X509::Certificate.new
104
+ cert.version = 2
105
+ cert.serial = 1
106
+ cert.subject = OpenSSL::X509::Name.parse("/CN=Test Signer")
107
+ cert.issuer = cert.subject
108
+ cert.public_key = key.public_key
109
+ cert.not_before = Time.now
110
+ cert.not_after = Time.now + 86_400
111
+ cert.sign(key, OpenSSL::Digest.new("SHA256"))
112
+ cert
106
113
  end
107
114
 
108
- private
115
+ # Builds a minimal valid PDF using only stdlib — no hexapdf or prawn.
116
+ def create_minimal_pdf(path)
117
+ hdr = "%PDF-1.4\n"
118
+ obj1 = "1 0 obj\n<</Type /Catalog /Pages 2 0 R>>\nendobj\n"
119
+ obj2 = "2 0 obj\n<</Type /Pages /Kids [3 0 R] /Count 1>>\nendobj\n"
120
+ obj3 = "3 0 obj\n<</Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]>>\nendobj\n"
121
+
122
+ o1 = hdr.bytesize
123
+ o2 = o1 + obj1.bytesize
124
+ o3 = o2 + obj2.bytesize
125
+ xref_off = o3 + obj3.bytesize
109
126
 
110
- def create_test_pdf(path)
111
- doc = HexaPDF::Document.new
112
- page = doc.pages.add
113
- page.canvas.font("Helvetica", size: 12)
114
- page.canvas.text("Test PDF Document", at: [100, 700])
115
- doc.write(path)
127
+ xref = "xref\n0 4\n" \
128
+ "0000000000 65535 f \n" \
129
+ "#{o1.to_s.rjust(10, "0")} 00000 n \n" \
130
+ "#{o2.to_s.rjust(10, "0")} 00000 n \n" \
131
+ "#{o3.to_s.rjust(10, "0")} 00000 n \n"
132
+
133
+ trailer = "trailer\n<</Size 4 /Root 1 0 R>>\nstartxref\n#{xref_off}\n%%EOF\n"
134
+
135
+ File.binwrite(path, hdr + obj1 + obj2 + obj3 + xref + trailer)
116
136
  end
117
137
  end
118
138
 
119
139
  class PdfVerificationTest < EasyCodeSignTest
120
140
  def setup
121
141
  @temp_pdf = Tempfile.new(["test", ".pdf"])
122
- create_test_pdf(@temp_pdf.path)
142
+ create_minimal_pdf(@temp_pdf.path)
123
143
  end
124
144
 
125
145
  def teardown
@@ -144,426 +164,30 @@ class PdfVerificationTest < EasyCodeSignTest
144
164
 
145
165
  private
146
166
 
147
- def create_test_pdf(path)
148
- doc = HexaPDF::Document.new
149
- page = doc.pages.add
150
- page.canvas.font("Helvetica", size: 12)
151
- page.canvas.text("Test PDF for verification", at: [100, 700])
152
- doc.write(path)
153
- end
154
-
155
167
  def assert_any_match(array, pattern)
156
168
  assert array.any? { |item| item.match?(pattern) },
157
169
  "Expected at least one item in #{array.inspect} to match #{pattern.inspect}"
158
170
  end
159
- end
160
-
161
- class PdfDeferredSigningTest < EasyCodeSignTest
162
- def setup
163
- super
164
- @temp_pdf = Tempfile.new(["deferred_test", ".pdf"])
165
- create_test_pdf(@temp_pdf.path)
166
- @key = OpenSSL::PKey::RSA.new(2048)
167
- @cert = create_test_cert(@key)
168
- @chain = [@cert]
169
- @cleanup_files = []
170
- end
171
-
172
- def teardown
173
- @temp_pdf.close
174
- @temp_pdf.unlink
175
- @cleanup_files.each { |f| File.delete(f) if File.exist?(f) }
176
- super
177
- end
178
-
179
- def test_prepare_deferred_returns_request
180
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
181
- request = signable.prepare_deferred(@cert, @chain)
182
- track_prepared(request)
183
-
184
- assert_instance_of EasyCodeSign::DeferredSigningRequest, request
185
- assert_instance_of String, request.digest
186
- assert_equal :sha256, request.digest_algorithm
187
- assert File.exist?(request.prepared_pdf_path)
188
- assert_instance_of Array, request.byte_range
189
- assert_equal 4, request.byte_range.size
190
- assert_equal @cert.to_pem, request.certificate.to_pem
191
- assert_instance_of Time, request.signing_time
192
- assert_instance_of Time, request.created_at
193
- end
194
-
195
- def test_prepare_deferred_creates_valid_pdf
196
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
197
- request = signable.prepare_deferred(@cert, @chain)
198
- track_prepared(request)
199
-
200
- assert File.exist?(request.prepared_pdf_path)
201
- header = File.open(request.prepared_pdf_path, "rb") { |f| f.read(5) }
202
- assert_equal "%PDF-", header
203
- end
204
-
205
- def test_prepare_deferred_digest_size
206
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
207
- request = signable.prepare_deferred(@cert, @chain, digest_algorithm: "sha256")
208
- track_prepared(request)
209
-
210
- assert_equal 32, request.digest.bytesize
211
- end
212
-
213
- def test_prepare_deferred_sha512_digest_size
214
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
215
- request = signable.prepare_deferred(@cert, @chain, digest_algorithm: "sha512")
216
- track_prepared(request)
217
-
218
- assert_equal 64, request.digest.bytesize
219
- end
220
-
221
- def test_deferred_request_serialization_roundtrip
222
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
223
- request = signable.prepare_deferred(@cert, @chain)
224
- track_prepared(request)
225
-
226
- hash = request.to_h
227
- restored = EasyCodeSign::DeferredSigningRequest.from_h(hash)
228
-
229
- assert_equal request.digest, restored.digest
230
- assert_equal request.digest_algorithm, restored.digest_algorithm
231
- assert_equal request.prepared_pdf_path, restored.prepared_pdf_path
232
- assert_equal request.byte_range, restored.byte_range
233
- assert_equal request.certificate.to_pem, restored.certificate.to_pem
234
- assert_equal request.certificate_chain.map(&:to_pem), restored.certificate_chain.map(&:to_pem)
235
- assert_equal request.estimated_size, restored.estimated_size
236
- assert_equal request.signing_time.to_i, restored.signing_time.to_i
237
- assert_equal request.created_at.to_i, restored.created_at.to_i
238
- end
239
-
240
- def test_deferred_request_digest_hex_and_base64
241
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
242
- request = signable.prepare_deferred(@cert, @chain)
243
- track_prepared(request)
244
-
245
- assert_equal request.digest.unpack1("H*"), request.digest_hex
246
- assert_equal Base64.strict_encode64(request.digest), request.digest_base64
247
- end
248
-
249
- def test_finalize_deferred_produces_signed_pdf
250
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
251
- request = signable.prepare_deferred(@cert, @chain)
252
- track_prepared(request)
253
-
254
- # Sign the digest with our test key (simulating external signer)
255
- raw_signature = @key.sign_raw("sha256", request.digest)
256
-
257
- finalizer = EasyCodeSign::Signable::PdfFile.new(request.prepared_pdf_path)
258
- signed_path = finalizer.finalize_deferred(request, raw_signature)
259
-
260
- assert File.exist?(signed_path)
261
-
262
- # Verify the signed PDF has a signature
263
- verifier_signable = EasyCodeSign::Signable::PdfFile.new(signed_path)
264
- sig = verifier_signable.extract_signature
265
- refute_nil sig, "Expected signed PDF to have an extractable signature"
266
- refute_nil sig[:contents]
267
- refute_nil sig[:byte_range]
268
- end
269
-
270
- def test_signed_attributes_data_present
271
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
272
- request = signable.prepare_deferred(@cert, @chain)
273
- track_prepared(request)
274
-
275
- refute_nil request.signed_attributes_data
276
- refute_empty request.signed_attributes_data
277
- refute_nil request.signed_attributes_base64
278
- end
279
-
280
- def test_signed_attributes_data_hash_matches_digest
281
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
282
- request = signable.prepare_deferred(@cert, @chain, digest_algorithm: "sha256")
283
- track_prepared(request)
284
-
285
- computed_hash = OpenSSL::Digest::SHA256.digest(request.signed_attributes_data)
286
- assert_equal request.digest, computed_hash,
287
- "SHA256(signed_attributes_data) must equal the captured digest"
288
- end
289
-
290
- def test_webcrypto_style_signing_produces_valid_pdf
291
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
292
- request = signable.prepare_deferred(@cert, @chain)
293
- track_prepared(request)
294
-
295
- # Simulate WebCrypto: hash-and-sign in one step (like crypto.subtle.sign)
296
- raw_signature = @key.sign("SHA256", request.signed_attributes_data)
297
-
298
- finalizer = EasyCodeSign::Signable::PdfFile.new(request.prepared_pdf_path)
299
- signed_path = finalizer.finalize_deferred(request, raw_signature)
300
-
301
- assert File.exist?(signed_path)
302
-
303
- verifier_signable = EasyCodeSign::Signable::PdfFile.new(signed_path)
304
- sig = verifier_signable.extract_signature
305
- refute_nil sig, "Expected signed PDF to have an extractable signature"
306
- refute_nil sig[:contents]
307
- end
308
-
309
- def test_signed_attributes_data_serialization_roundtrip
310
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
311
- request = signable.prepare_deferred(@cert, @chain)
312
- track_prepared(request)
313
-
314
- hash = request.to_h
315
- assert hash.key?("signed_attributes_data"), "to_h should include signed_attributes_data"
316
-
317
- restored = EasyCodeSign::DeferredSigningRequest.from_h(hash)
318
- assert_equal request.signed_attributes_data, restored.signed_attributes_data
319
- end
320
-
321
- def test_finalize_deferred_raises_on_missing_pdf
322
- request = EasyCodeSign::DeferredSigningRequest.new(
323
- digest: "\x00" * 32,
324
- digest_algorithm: :sha256,
325
- prepared_pdf_path: "/nonexistent/path/missing.pdf",
326
- byte_range: [0, 100, 200, 100],
327
- certificate: @cert,
328
- certificate_chain: @chain,
329
- estimated_size: 8192,
330
- signing_time: Time.now
331
- )
332
-
333
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
334
-
335
- assert_raises(EasyCodeSign::DeferredSigningError) do
336
- signable.finalize_deferred(request, "fake_sig")
337
- end
338
- end
339
-
340
- def test_finalize_deferred_raises_on_oversized_signature
341
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
342
- request = signable.prepare_deferred(@cert, @chain)
343
- track_prepared(request)
344
-
345
- # Create a signature that's way too large for the reserved space
346
- oversized_signature = "\xFF" * (request.estimated_size * 3)
347
-
348
- finalizer = EasyCodeSign::Signable::PdfFile.new(request.prepared_pdf_path)
349
-
350
- assert_raises(EasyCodeSign::DeferredSigningError) do
351
- finalizer.finalize_deferred(request, oversized_signature)
352
- end
353
- end
354
-
355
- def test_finalize_deferred_with_timestamp_embeds_token
356
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
357
- request = signable.prepare_deferred(@cert, @chain, timestamp_size: 4096)
358
- track_prepared(request)
359
-
360
- raw_signature = @key.sign_raw("sha256", request.digest)
361
- mock_token = create_mock_timestamp_token
362
-
363
- finalizer = EasyCodeSign::Signable::PdfFile.new(request.prepared_pdf_path)
364
- signed_path = finalizer.finalize_deferred(request, raw_signature, timestamp_token: mock_token)
365
-
366
- assert File.exist?(signed_path)
367
-
368
- # Parse the CMS from the signed PDF and check for timestamp unsigned attribute
369
- verifier_signable = EasyCodeSign::Signable::PdfFile.new(signed_path)
370
- sig = verifier_signable.extract_signature
371
- refute_nil sig, "Expected signed PDF to have a signature"
372
-
373
- # Decode the CMS DER (trimming zero-padding) and look for id-aa-timeStampToken OID
374
- cms_asn1 = decode_cms_from_contents(sig[:contents])
375
- assert_timestamp_attribute_present(cms_asn1)
376
- end
377
-
378
- def test_finalize_deferred_without_timestamp_unchanged
379
- signable = EasyCodeSign::Signable::PdfFile.new(@temp_pdf.path)
380
- request = signable.prepare_deferred(@cert, @chain)
381
- track_prepared(request)
382
-
383
- raw_signature = @key.sign_raw("sha256", request.digest)
384
-
385
- finalizer = EasyCodeSign::Signable::PdfFile.new(request.prepared_pdf_path)
386
- signed_path = finalizer.finalize_deferred(request, raw_signature)
387
-
388
- assert File.exist?(signed_path)
389
-
390
- verifier_signable = EasyCodeSign::Signable::PdfFile.new(signed_path)
391
- sig = verifier_signable.extract_signature
392
- refute_nil sig
393
-
394
- # Verify no timestamp unsigned attribute is present
395
- cms_asn1 = decode_cms_from_contents(sig[:contents])
396
- refute_timestamp_attribute_present(cms_asn1)
397
- end
398
-
399
- private
400
-
401
- def track_prepared(request)
402
- @cleanup_files << request.prepared_pdf_path
403
- end
404
171
 
405
- def create_test_pdf(path)
406
- doc = HexaPDF::Document.new
407
- page = doc.pages.add
408
- page.canvas.font("Helvetica", size: 12)
409
- page.canvas.text("Deferred signing test document", at: [100, 700])
410
- doc.write(path)
411
- end
172
+ def create_minimal_pdf(path)
173
+ hdr = "%PDF-1.4\n"
174
+ obj1 = "1 0 obj\n<</Type /Catalog /Pages 2 0 R>>\nendobj\n"
175
+ obj2 = "2 0 obj\n<</Type /Pages /Kids [3 0 R] /Count 1>>\nendobj\n"
176
+ obj3 = "3 0 obj\n<</Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]>>\nendobj\n"
412
177
 
413
- def create_test_cert(key)
414
- cert = OpenSSL::X509::Certificate.new
415
- cert.version = 2
416
- cert.serial = 1
417
- cert.subject = OpenSSL::X509::Name.parse("/CN=Test Deferred Signer")
418
- cert.issuer = cert.subject
419
- cert.public_key = key.public_key
420
- cert.not_before = Time.now
421
- cert.not_after = Time.now + 86_400
422
- cert.sign(key, OpenSSL::Digest.new("SHA256"))
423
- cert
424
- end
178
+ o1 = hdr.bytesize
179
+ o2 = o1 + obj1.bytesize
180
+ o3 = o2 + obj2.bytesize
181
+ xref_off = o3 + obj3.bytesize
425
182
 
426
- def create_mock_timestamp_token
427
- # Build a minimal self-signed PKCS#7 structure as mock token DER
428
- tsa_key = OpenSSL::PKey::RSA.new(2048)
429
- tsa_cert = OpenSSL::X509::Certificate.new
430
- tsa_cert.version = 2
431
- tsa_cert.serial = 99
432
- tsa_cert.subject = OpenSSL::X509::Name.parse("/CN=Mock TSA")
433
- tsa_cert.issuer = tsa_cert.subject
434
- tsa_cert.public_key = tsa_key.public_key
435
- tsa_cert.not_before = Time.now
436
- tsa_cert.not_after = Time.now + 86_400
437
- tsa_cert.sign(tsa_key, OpenSSL::Digest.new("SHA256"))
438
-
439
- pkcs7 = OpenSSL::PKCS7.sign(tsa_cert, tsa_key, "mock timestamp data",
440
- [], OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
441
-
442
- EasyCodeSign::Timestamp::TimestampToken.new(
443
- token_der: pkcs7.to_der,
444
- timestamp: Time.now,
445
- serial_number: 99,
446
- policy_oid: "1.2.3.4",
447
- tsa_url: "http://test.tsa"
448
- )
449
- end
183
+ xref = "xref\n0 4\n" \
184
+ "0000000000 65535 f \n" \
185
+ "#{o1.to_s.rjust(10, "0")} 00000 n \n" \
186
+ "#{o2.to_s.rjust(10, "0")} 00000 n \n" \
187
+ "#{o3.to_s.rjust(10, "0")} 00000 n \n"
450
188
 
451
- # Decode CMS DER from PDF /Contents (which may have trailing zero-padding)
452
- def decode_cms_from_contents(contents)
453
- # The PDF reserves a fixed-size space; trailing zeros must be stripped.
454
- # Parse just the first valid DER structure using decode_all which ignores trailing data.
455
- OpenSSL::ASN1.decode(contents.sub(/\x00+\z/, ""))
456
- end
189
+ trailer = "trailer\n<</Size 4 /Root 1 0 R>>\nstartxref\n#{xref_off}\n%%EOF\n"
457
190
 
458
- # Recursively search the ASN.1 tree for the id-aa-timeStampToken OID
459
- TIMESTAMP_TOKEN_OID = "1.2.840.113549.1.9.16.2.14"
460
-
461
- def find_oid_in_asn1(asn1, target_oid)
462
- if asn1.is_a?(OpenSSL::ASN1::ObjectId) && asn1.oid == target_oid
463
- return true
464
- end
465
-
466
- if asn1.respond_to?(:value) && asn1.value.is_a?(Array)
467
- asn1.value.any? { |child| find_oid_in_asn1(child, target_oid) }
468
- else
469
- false
470
- end
471
- end
472
-
473
- def assert_timestamp_attribute_present(cms_asn1)
474
- assert find_oid_in_asn1(cms_asn1, TIMESTAMP_TOKEN_OID),
475
- "Expected CMS to contain id-aa-timeStampToken unsigned attribute"
476
- end
477
-
478
- def refute_timestamp_attribute_present(cms_asn1)
479
- refute find_oid_in_asn1(cms_asn1, TIMESTAMP_TOKEN_OID),
480
- "Expected CMS NOT to contain id-aa-timeStampToken unsigned attribute"
481
- end
482
- end
483
-
484
- class PdfTimestampHandlerTest < Minitest::Test
485
- def test_sign_returns_token_der_from_eager_token
486
- mock_token = Struct.new(:token_der).new("mock_der_bytes")
487
- handler = EasyCodeSign::Pdf::TimestampHandler.new(mock_token)
488
-
489
- result = handler.sign(StringIO.new, [0, 0, 0, 0])
490
- assert_equal "mock_der_bytes", result
491
- end
492
-
493
- def test_sign_returns_token_der_from_lazy_proc
494
- mock_token = Struct.new(:token_der).new("lazy_der_bytes")
495
- handler = EasyCodeSign::Pdf::TimestampHandler.new(-> { mock_token })
496
-
497
- result = handler.sign(StringIO.new, [0, 0, 0, 0])
498
- assert_equal "lazy_der_bytes", result
499
- end
500
-
501
- def test_sign_returns_nil_when_token_is_nil
502
- handler = EasyCodeSign::Pdf::TimestampHandler.new(nil)
503
-
504
- result = handler.sign(StringIO.new, [0, 0, 0, 0])
505
- assert_nil result
506
- end
507
-
508
- def test_sign_returns_nil_when_lazy_proc_returns_nil
509
- handler = EasyCodeSign::Pdf::TimestampHandler.new(-> { nil })
510
-
511
- result = handler.sign(StringIO.new, [0, 0, 0, 0])
512
- assert_nil result
513
- end
514
- end
515
-
516
- class ExternalSigningCallbackTest < Minitest::Test
517
- def test_callback_receives_data_and_returns_signature
518
- expected_signature = "mock_signature"
519
- callback = EasyCodeSign::Signable::ExternalSigningCallback.new(->(data) { expected_signature })
520
-
521
- result = callback.sign("test data", "SHA256")
522
- assert_equal expected_signature, result
523
- end
524
-
525
- def test_callback_is_private
526
- callback = EasyCodeSign::Signable::ExternalSigningCallback.new(->(_) { "sig" })
527
- assert callback.private?
528
- end
529
- end
530
-
531
- class ExternalSigningProxyTest < Minitest::Test
532
- def setup
533
- @key = OpenSSL::PKey::RSA.new(2048)
534
- @cert = create_test_cert(@key)
535
- end
536
-
537
- def test_proxy_returns_precomputed_signature
538
- signature = "precomputed_signature"
539
- proxy = EasyCodeSign::Signable::ExternalSigningProxy.new(signature, @cert, [@cert])
540
-
541
- result = proxy.sign("any data", "SHA256")
542
- assert_equal signature, result
543
- end
544
-
545
- def test_proxy_has_certificate
546
- proxy = EasyCodeSign::Signable::ExternalSigningProxy.new("sig", @cert, [@cert])
547
- assert_equal @cert, proxy.certificate
548
- end
549
-
550
- def test_proxy_is_private
551
- proxy = EasyCodeSign::Signable::ExternalSigningProxy.new("sig", @cert, [@cert])
552
- assert proxy.private?
553
- end
554
-
555
- private
556
-
557
- def create_test_cert(key)
558
- cert = OpenSSL::X509::Certificate.new
559
- cert.version = 2
560
- cert.serial = 1
561
- cert.subject = OpenSSL::X509::Name.parse("/CN=Test")
562
- cert.issuer = cert.subject
563
- cert.public_key = key.public_key
564
- cert.not_before = Time.now
565
- cert.not_after = Time.now + 86_400
566
- cert.sign(key, OpenSSL::Digest.new("SHA256"))
567
- cert
191
+ File.binwrite(path, hdr + obj1 + obj2 + obj3 + xref + trailer)
568
192
  end
569
193
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easy_code_sign
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - michail
@@ -24,19 +24,19 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0.2'
26
26
  - !ruby/object:Gem::Dependency
27
- name: hexapdf
27
+ name: pdf-reader
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '1.0'
32
+ version: '2.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '1.0'
39
+ version: '2.0'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: pkcs11
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -142,6 +142,8 @@ files:
142
142
  - lib/easy_code_sign/deferred_signing_request.rb
143
143
  - lib/easy_code_sign/errors.rb
144
144
  - lib/easy_code_sign/pdf/appearance_builder.rb
145
+ - lib/easy_code_sign/pdf/cms_builder.rb
146
+ - lib/easy_code_sign/pdf/native_signer.rb
145
147
  - lib/easy_code_sign/pdf/timestamp_handler.rb
146
148
  - lib/easy_code_sign/providers/base.rb
147
149
  - lib/easy_code_sign/providers/pkcs11_base.rb