hexapdf 0.28.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -10
  3. data/examples/024-digital-signatures.rb +23 -0
  4. data/lib/hexapdf/configuration.rb +12 -12
  5. data/lib/hexapdf/dictionary_fields.rb +6 -2
  6. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  7. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  8. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  9. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  10. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  11. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  12. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  13. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  14. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  15. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  16. data/lib/hexapdf/digital_signature.rb +56 -0
  17. data/lib/hexapdf/document.rb +21 -14
  18. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
  19. data/lib/hexapdf/type.rb +0 -1
  20. data/lib/hexapdf/version.rb +1 -1
  21. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  22. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  23. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  24. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  25. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  26. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  27. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  28. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  29. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  30. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  31. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  32. data/test/hexapdf/test_dictionary_fields.rb +2 -1
  33. data/test/hexapdf/test_document.rb +1 -1
  34. data/test/hexapdf/test_writer.rb +3 -3
  35. metadata +25 -15
  36. data/lib/hexapdf/document/signatures.rb +0 -546
  37. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  38. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  39. data/lib/hexapdf/type/signature/handler.rb +0 -140
  40. data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -0,0 +1,56 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ module HexaPDF
38
+
39
+ # PDF documents can be signed using digital signatures. Such a signature can be used to
40
+ # authenticate the identity of the signer and the contents of the documents.
41
+ #
42
+ # This module contains all code related to digital signatures in PDF.
43
+ #
44
+ # See: PDF1.7/2.0 s12.8
45
+ module DigitalSignature
46
+
47
+ autoload(:Signatures, 'hexapdf/digital_signature/signatures')
48
+ autoload(:Signature, "hexapdf/digital_signature/signature")
49
+ autoload(:Handler, 'hexapdf/digital_signature/handler')
50
+ autoload(:CMSHandler, "hexapdf/digital_signature/cms_handler")
51
+ autoload(:PKCS1Handler, "hexapdf/digital_signature/pkcs1_handler")
52
+ autoload(:VerificationResult, 'hexapdf/digital_signature/verification_result')
53
+ autoload(:Signing, 'hexapdf/digital_signature/signing')
54
+
55
+ end
56
+ end
@@ -52,6 +52,7 @@ require 'hexapdf/importer'
52
52
  require 'hexapdf/image_loader'
53
53
  require 'hexapdf/font_loader'
54
54
  require 'hexapdf/layout'
55
+ require 'hexapdf/digital_signature'
55
56
 
56
57
  begin
57
58
  require 'hexapdf/cext'
@@ -105,7 +106,6 @@ module HexaPDF
105
106
  autoload(:Fonts, 'hexapdf/document/fonts')
106
107
  autoload(:Images, 'hexapdf/document/images')
107
108
  autoload(:Files, 'hexapdf/document/files')
108
- autoload(:Signatures, 'hexapdf/document/signatures')
109
109
  autoload(:Destinations, 'hexapdf/document/destinations')
110
110
  autoload(:Layout, 'hexapdf/document/layout')
111
111
 
@@ -152,15 +152,19 @@ module HexaPDF
152
152
  #
153
153
  # Options:
154
154
  #
155
- # io:: If an IO object is provided, then this document can read PDF objects from this IO
156
- # object, otherwise it can only contain created PDF objects.
155
+ # io::
156
+ # If an IO object is provided, then this document can read PDF objects from this IO object,
157
+ # otherwise it can only contain created PDF objects.
157
158
  #
158
- # decryption_opts:: A hash with options for decrypting the PDF objects loaded from the IO.
159
+ # decryption_opts::
160
+ # A hash with options for decrypting the PDF objects loaded from the IO. The PDF standard
161
+ # security handler expects a :password key to be set to either the user or owner password of
162
+ # the PDF file.
159
163
  #
160
- # config:: A hash with configuration options that is deep-merged into the default configuration
161
- # (see
162
- # HexaPDF::DefaultDocumentConfiguration[../index.html#DefaultDocumentConfiguration],
163
- # meaning that direct sub-hashes are merged instead of overwritten.
164
+ # config::
165
+ # A hash with configuration options that is deep-merged into the default configuration (see
166
+ # HexaPDF::DefaultDocumentConfiguration[../index.html#DefaultDocumentConfiguration], meaning
167
+ # that direct sub-hashes are merged instead of overwritten.
164
168
  def initialize(io: nil, decryption_opts: {}, config: {})
165
169
  @config = Configuration.with_defaults(config)
166
170
  @version = '1.2'
@@ -585,25 +589,28 @@ module HexaPDF
585
589
  acro_form&.signature_flag?(:signatures_exist)
586
590
  end
587
591
 
588
- # Returns an array with the digital signatures of this document.
592
+ # Returns a DigitalSignature::Signatures object that allows working with the digital signatures
593
+ # of this document.
589
594
  def signatures
590
- @signatures ||= Signatures.new(self)
595
+ @signatures ||= DigitalSignature::Signatures.new(self)
591
596
  end
592
597
 
593
598
  # Signs the document and writes it to the given file or IO object.
594
599
  #
595
600
  # For details on the arguments +file_or_io+, +signature+ and +write_options+ see
596
- # HexaPDF::Document::Signatures#add.
601
+ # HexaPDF::DigitalSignature::Signatures#add.
597
602
  #
598
603
  # The signing handler to be used is determined by the +handler+ argument together with the rest
599
- # of the keyword arguments (see HexaPDF::Document::Signatures#handler for details).
604
+ # of the keyword arguments (see HexaPDF::DigitalSignature::Signatures#signing_handler for
605
+ # details).
600
606
  #
601
- # If not changed, the default signing handler is HexaPDF::Document::Signatures::DefaultHandler.
607
+ # If not changed, the default signing handler is
608
+ # HexaPDF::DigitalSignature::Signing::DefaultHandler.
602
609
  #
603
610
  # *Note*: Once signing is done the document cannot be changed anymore since it was written. If a
604
611
  # document needs to be signed multiple times, it needs to be loaded again after writing.
605
612
  def sign(file_or_io, handler: :default, signature: nil, write_options: {}, **handler_options)
606
- handler = signatures.handler(name: handler, **handler_options)
613
+ handler = signatures.signing_handler(name: handler, **handler_options)
607
614
  signatures.add(file_or_io, handler, signature: signature, write_options: write_options)
608
615
  end
609
616
 
@@ -97,7 +97,8 @@ module HexaPDF
97
97
  # a user is allowed to do with a PDF file.
98
98
  #
99
99
  # When a user or owner password is specified, a PDF file can only be opened when the correct
100
- # password is supplied.
100
+ # password is supplied. To open such an encrypted PDF file, the +decryption_opts+ provided to
101
+ # HexaPDF::Document.new needs to contain a :password key with the password.
101
102
  #
102
103
  # See: PDF1.7 s7.6.3, PDF2.0 s7.6.3
103
104
  class StandardSecurityHandler < SecurityHandler
data/lib/hexapdf/type.rb CHANGED
@@ -72,7 +72,6 @@ module HexaPDF
72
72
  autoload(:FontType3, 'hexapdf/type/font_type3')
73
73
  autoload(:IconFit, 'hexapdf/type/icon_fit')
74
74
  autoload(:AcroForm, 'hexapdf/type/acro_form')
75
- autoload(:Signature, 'hexapdf/type/signature')
76
75
  autoload(:Outline, 'hexapdf/type/outline')
77
76
  autoload(:OutlineItem, 'hexapdf/type/outline_item')
78
77
  autoload(:PageLabel, 'hexapdf/type/page_label')
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.28.0'
40
+ VERSION = '0.29.0'
41
41
 
42
42
  end
@@ -6,7 +6,7 @@ module HexaPDF
6
6
  class Certificates
7
7
 
8
8
  def ca_key
9
- @ca_key ||= OpenSSL::PKey::RSA.new(512)
9
+ @ca_key ||= OpenSSL::PKey::RSA.new(2048)
10
10
  end
11
11
 
12
12
  def ca_certificate
@@ -36,13 +36,17 @@ module HexaPDF
36
36
  end
37
37
 
38
38
  def signer_key
39
- @signer_key ||= OpenSSL::PKey::RSA.new(512)
39
+ @signer_key ||= OpenSSL::PKey::RSA.new(2048)
40
+ end
41
+
42
+ def dsa_signer_key
43
+ @dsa_signer_key ||= OpenSSL::PKey::DSA.new(2048)
40
44
  end
41
45
 
42
46
  def signer_certificate
43
47
  @signer_certificate ||=
44
48
  begin
45
- name = OpenSSL::X509::Name.parse('/CN=signer/DC=gettalong')
49
+ name = OpenSSL::X509::Name.parse('/CN=RSA signer/DC=gettalong')
46
50
 
47
51
  signer_cert = OpenSSL::X509::Certificate.new
48
52
  signer_cert.serial = 2
@@ -65,6 +69,30 @@ module HexaPDF
65
69
  end
66
70
  end
67
71
 
72
+ def dsa_signer_certificate
73
+ @dsa_signer_certificate ||=
74
+ begin
75
+ signer_cert = OpenSSL::X509::Certificate.new
76
+ signer_cert.serial = 3
77
+ signer_cert.version = 2
78
+ signer_cert.not_before = Time.now - 86400
79
+ signer_cert.not_after = Time.now + 86400
80
+ signer_cert.public_key = dsa_signer_key.public_key
81
+ signer_cert.subject = OpenSSL::X509::Name.parse('/CN=DSA signer/DC=gettalong')
82
+ signer_cert.issuer = ca_certificate.subject
83
+
84
+ extension_factory = OpenSSL::X509::ExtensionFactory.new
85
+ extension_factory.subject_certificate = signer_cert
86
+ extension_factory.issuer_certificate = ca_certificate
87
+ signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
88
+ signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
89
+ signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
90
+ signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
91
+
92
+ signer_cert
93
+ end
94
+ end
95
+
68
96
  def timestamp_certificate
69
97
  @timestamp_certificate ||=
70
98
  begin
@@ -0,0 +1,162 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require_relative '../common'
6
+
7
+ describe HexaPDF::DigitalSignature::Signing::DefaultHandler do
8
+ before do
9
+ @doc = HexaPDF::Document.new
10
+ @handler = HexaPDF::DigitalSignature::Signing::DefaultHandler.new(
11
+ certificate: CERTIFICATES.signer_certificate,
12
+ key: CERTIFICATES.signer_key,
13
+ certificate_chain: [CERTIFICATES.ca_certificate]
14
+ )
15
+ end
16
+
17
+ it "defaults to standard CMS signatures" do
18
+ assert_equal(:cms, @handler.signature_type)
19
+ end
20
+
21
+ it "returns the size of serialized signature" do
22
+ assert(@handler.signature_size > 1000)
23
+ @handler.signature_size = 100
24
+ assert_equal(100, @handler.signature_size)
25
+ end
26
+
27
+ it "allows setting the DocMDP permissions" do
28
+ assert_nil(@handler.doc_mdp_permissions)
29
+
30
+ @handler.doc_mdp_permissions = :no_changes
31
+ assert_equal(1, @handler.doc_mdp_permissions)
32
+ @handler.doc_mdp_permissions = 1
33
+ assert_equal(1, @handler.doc_mdp_permissions)
34
+
35
+ @handler.doc_mdp_permissions = :form_filling
36
+ assert_equal(2, @handler.doc_mdp_permissions)
37
+ @handler.doc_mdp_permissions = 2
38
+ assert_equal(2, @handler.doc_mdp_permissions)
39
+
40
+ @handler.doc_mdp_permissions = :form_filling_and_annotations
41
+ assert_equal(3, @handler.doc_mdp_permissions)
42
+ @handler.doc_mdp_permissions = 3
43
+ assert_equal(3, @handler.doc_mdp_permissions)
44
+
45
+ @handler.doc_mdp_permissions = nil
46
+ assert_nil(@handler.doc_mdp_permissions)
47
+
48
+ assert_raises(ArgumentError) { @handler.doc_mdp_permissions = :other }
49
+ end
50
+
51
+ describe "sign" do
52
+ it "can sign the data using the provided certificate and key" do
53
+ data = StringIO.new("data")
54
+ signed_data = @handler.sign(data, [0, data.string.size, 0, 0])
55
+
56
+ pkcs7 = OpenSSL::PKCS7.new(signed_data)
57
+ assert(pkcs7.detached?)
58
+ assert_equal([CERTIFICATES.signer_certificate, CERTIFICATES.ca_certificate],
59
+ pkcs7.certificates)
60
+ store = OpenSSL::X509::Store.new
61
+ store.add_cert(CERTIFICATES.ca_certificate)
62
+ assert(pkcs7.verify([], store, data.string, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY))
63
+ end
64
+
65
+ it "can change the used digest algorithm" do
66
+ @handler.digest_algorithm = 'sha384'
67
+ asn1 = OpenSSL::ASN1.decode(@handler.sign(StringIO.new('data'), [0, 4, 0, 0]))
68
+ assert_equal('SHA384', asn1.value[1].value[0].value[1].value[0].value[0].value)
69
+ end
70
+
71
+ it "can embed a timestamp token" do
72
+ @handler.timestamp_handler = tsh = Object.new
73
+ tsh.define_singleton_method(:sign) {|_, _| OpenSSL::ASN1::OctetString.new("signed-tsh") }
74
+ signed = @handler.sign(StringIO.new('data'), [0, 4, 0, 0])
75
+ asn1 = OpenSSL::ASN1.decode(signed)
76
+ assert_equal('signed-tsh', asn1.value[1].value[0].value[4].value[0].
77
+ value[6].value[0].value[1].value[0].value)
78
+ end
79
+
80
+ it "creates PAdES compatible signatures" do
81
+ @handler.signature_type = :pades
82
+ signed = @handler.sign(StringIO.new('data'), [0, 4, 0, 0])
83
+ asn1 = OpenSSL::ASN1.decode(signed)
84
+ # check by absence of signing-time signed attribute
85
+ refute(asn1.value[1].value[0].value[4].value[0].value[3].value.
86
+ find {|obj| obj.value[0].value == 'signingTime' })
87
+ end
88
+
89
+ it "can use external signing without certificate set" do
90
+ @handler.certificate = nil
91
+ @handler.external_signing = proc { "hallo" }
92
+ assert_equal("hallo", @handler.sign(StringIO.new, [0, 0, 0, 0]))
93
+ end
94
+
95
+ it "can use external signing with certificate set but not the key" do
96
+ @handler.key = nil
97
+ @handler.external_signing = proc do |algorithm, _hash|
98
+ assert_equal('sha256', algorithm)
99
+ "hallo"
100
+ end
101
+ result = @handler.sign(StringIO.new, [0, 0, 0, 0])
102
+ asn1 = OpenSSL::ASN1.decode(result)
103
+ assert_equal("hallo", asn1.value[1].value[0].value[4].value[0].value[5].value)
104
+ end
105
+ end
106
+
107
+ describe "finalize_objects" do
108
+ before do
109
+ @field = @doc.wrap({})
110
+ @obj = @doc.wrap({})
111
+ end
112
+
113
+ it "only sets the mandatory values if no concrete finalization tasks need to be done" do
114
+ @handler.finalize_objects(@field, @obj)
115
+ assert(@field.empty?)
116
+ assert_equal(:'Adobe.PPKLite', @obj[:Filter])
117
+ assert_equal(:'adbe.pkcs7.detached', @obj[:SubFilter])
118
+ assert_kind_of(Time, @obj[:M])
119
+ end
120
+
121
+ it "adjust the /SubFilter if signature type is pades" do
122
+ @handler.signature_type = :pades
123
+ @handler.finalize_objects(@field, @obj)
124
+ assert_equal(:'ETSI.CAdES.detached', @obj[:SubFilter])
125
+ end
126
+
127
+ it "sets the reason, location and contact info fields" do
128
+ @handler.reason = 'Reason'
129
+ @handler.location = 'Location'
130
+ @handler.contact_info = 'Contact'
131
+ @handler.finalize_objects(@field, @obj)
132
+ assert(@field.empty?)
133
+ assert_equal(['Reason', 'Location', 'Contact'], @obj.value.values_at(:Reason, :Location, :ContactInfo))
134
+ end
135
+
136
+ it "fills the build properties dictionary with appropriate application information" do
137
+ @handler.finalize_objects(@field, @obj)
138
+ assert_equal(:HexaPDF, @obj[:Prop_Build][:App][:Name])
139
+ assert_equal(HexaPDF::VERSION, @obj[:Prop_Build][:App][:REx])
140
+ end
141
+
142
+ it "applies the specified DocMDP permissions" do
143
+ @handler.doc_mdp_permissions = :no_changes
144
+ @handler.finalize_objects(@field, @obj)
145
+ ref = @obj[:Reference][0]
146
+ assert_equal(:DocMDP, ref[:TransformMethod])
147
+ assert_equal(:SHA256, ref[:DigestMethod])
148
+ assert_equal(1, ref[:TransformParams][:P])
149
+ assert_equal(:'1.2', ref[:TransformParams][:V])
150
+ assert_same(@obj, @doc.catalog[:Perms][:DocMDP])
151
+ end
152
+
153
+ it "fails if DocMDP should be set but there is already a signature" do
154
+ @handler.doc_mdp_permissions = :no_changes
155
+ 2.times do
156
+ field = @doc.acro_form(create: true).create_signature_field('test')
157
+ field.field_value = :something
158
+ end
159
+ assert_raises(HexaPDF::Error) { @handler.finalize_objects(@field, @obj) }
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,225 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require 'hexapdf/document'
5
+ require_relative '../common'
6
+
7
+ describe HexaPDF::DigitalSignature::Signing::SignedDataCreator do
8
+ before do
9
+ @klass = HexaPDF::DigitalSignature::Signing::SignedDataCreator
10
+ @signed_data = @klass.new
11
+ @signed_data.certificate = CERTIFICATES.signer_certificate
12
+ @signed_data.key = CERTIFICATES.signer_key
13
+ @signed_data.certificates = [CERTIFICATES.ca_certificate]
14
+ end
15
+
16
+ it "allows setting the attributes" do
17
+ obj = @klass.new
18
+ obj.certificate = :cert
19
+ obj.key = :key
20
+ obj.certificates = :certs
21
+ obj.digest_algorithm = 'sha512'
22
+ obj.timestamp_handler = :tsh
23
+ assert_equal(:cert, obj.certificate)
24
+ assert_equal(:key, obj.key)
25
+ assert_equal(:certs, obj.certificates)
26
+ assert_equal('sha512', obj.digest_algorithm)
27
+ assert_equal(:tsh, obj.timestamp_handler)
28
+ end
29
+
30
+ it "doesn't allow setting attributes to nil using ::create" do
31
+ asn1 = @klass.create("data",
32
+ certificate: CERTIFICATES.signer_certificate,
33
+ key: CERTIFICATES.signer_key,
34
+ digest_algorithm: nil)
35
+ assert_equal('2.16.840.1.101.3.4.2.1', asn1.value[1].value[1].value[0].value[0].value)
36
+ end
37
+
38
+ describe "content info structure" do
39
+ it "sets the correct content type value for the outer container" do
40
+ asn1 = @signed_data.create("data")
41
+ assert_equal('1.2.840.113549.1.7.2', asn1.value[0].value)
42
+ end
43
+
44
+ it "has the signed data structure marked as explicit" do
45
+ asn1 = @signed_data.create("data")
46
+ signed_data = asn1.value[1]
47
+ assert_equal(0, signed_data.tag)
48
+ assert_equal(:EXPLICIT, signed_data.tagging)
49
+ assert_equal(:CONTEXT_SPECIFIC, signed_data.tag_class)
50
+ end
51
+ end
52
+
53
+ describe "signed data structure" do
54
+ before do
55
+ @structure = @signed_data.create("data").value[1]
56
+ end
57
+
58
+ it "sets the correct version" do
59
+ assert_equal(1, @structure.value[0].value)
60
+ end
61
+
62
+ it "contains a reference to the used digest algorithm" do
63
+ assert_equal('2.16.840.1.101.3.4.2.1', @structure.value[1].value[0].value[0].value)
64
+ assert_nil(@structure.value[1].value[0].value[1].value)
65
+ end
66
+
67
+ it "contains an empty encapsulated content structure" do
68
+ assert_equal(1, @structure.value[2].value.size)
69
+ assert_equal('1.2.840.113549.1.7.1', @structure.value[2].value[0].value)
70
+ end
71
+
72
+ it "contains the assigned certificates" do
73
+ assert_equal(2, @structure.value[3].value.size)
74
+ assert_equal(0, @structure.value[3].tag)
75
+ assert_equal(:IMPLICIT, @structure.value[3].tagging)
76
+ assert_equal(:CONTEXT_SPECIFIC, @structure.value[3].tag_class)
77
+ assert_equal([CERTIFICATES.signer_certificate, CERTIFICATES.ca_certificate],
78
+ @structure.value[3].value)
79
+ end
80
+
81
+ it "contains a single signer info structure" do
82
+ assert_equal(1, @structure.value[4].value.size)
83
+ end
84
+ end
85
+
86
+ describe "signer info" do
87
+ before do
88
+ @structure = @signed_data.create("data").value[1].value[4].value[0]
89
+ end
90
+
91
+ it "has the expected number of entries" do
92
+ assert_equal(6, @structure.value.size)
93
+ end
94
+
95
+ it "sets the correct version" do
96
+ assert_equal(1, @structure.value[0].value)
97
+ end
98
+
99
+ it "uses issuer and serial for the signer identifer" do
100
+ assert_equal(CERTIFICATES.signer_certificate.issuer, @structure.value[1].value[0])
101
+ assert_equal(CERTIFICATES.signer_certificate.serial, @structure.value[1].value[1].value)
102
+ end
103
+
104
+ it "contains a reference to the used digest algorithm" do
105
+ assert_equal('2.16.840.1.101.3.4.2.1', @structure.value[2].value[0].value)
106
+ assert_nil(@structure.value[2].value[1].value)
107
+ end
108
+
109
+ describe "signed attributes" do
110
+ it "uses the correct tagging for the attributes" do
111
+ assert_equal(0, @structure.value[3].tag)
112
+ assert_equal(:IMPLICIT, @structure.value[3].tagging)
113
+ assert_equal(:CONTEXT_SPECIFIC, @structure.value[3].tag_class)
114
+ end
115
+
116
+ it "contains the content type identifier" do
117
+ attr = @structure.value[3].value.find {|obj| obj.value[0].value == '1.2.840.113549.1.9.3' }
118
+ assert_equal('1.2.840.113549.1.7.1', attr.value[1].value[0].value)
119
+ end
120
+
121
+ it "contains the message digest attribute" do
122
+ attr = @structure.value[3].value.find {|obj| obj.value[0].value == '1.2.840.113549.1.9.4' }
123
+ assert_equal(OpenSSL::Digest.digest('SHA256', 'data'), attr.value[1].value[0].value)
124
+ end
125
+
126
+ it "contains the signing certificate attribute" do
127
+ attr = @structure.value[3].value.find {|obj| obj.value[0].value == '1.2.840.113549.1.9.16.2.47' }
128
+ signing_cert = attr.value[1].value[0]
129
+ assert_equal(1, signing_cert.value.size)
130
+ assert_equal(1, signing_cert.value[0].value.size)
131
+ assert_equal(2, signing_cert.value[0].value[0].value.size)
132
+ assert_equal(OpenSSL::Digest.digest('sha256', CERTIFICATES.signer_certificate.to_der),
133
+ signing_cert.value[0].value[0].value[0].value)
134
+ assert_equal(2, signing_cert.value[0].value[0].value[1].value.size)
135
+ assert_equal(1, signing_cert.value[0].value[0].value[1].value[0].value.size)
136
+ assert_equal(1, signing_cert.value[0].value[0].value[1].value[0].value[0].value.size)
137
+ assert_equal(4, signing_cert.value[0].value[0].value[1].value[0].value[0].tag)
138
+ assert_equal(:IMPLICIT, signing_cert.value[0].value[0].value[1].value[0].value[0].tagging)
139
+ assert_equal(:CONTEXT_SPECIFIC, signing_cert.value[0].value[0].value[1].value[0].value[0].tag_class)
140
+ assert_equal(CERTIFICATES.signer_certificate.issuer,
141
+ signing_cert.value[0].value[0].value[1].value[0].value[0].value[0])
142
+ assert_equal(CERTIFICATES.signer_certificate.serial,
143
+ signing_cert.value[0].value[0].value[1].value[1].value)
144
+ end
145
+ end
146
+
147
+ it "contains the signature algorithm reference" do
148
+ assert_equal('1.2.840.113549.1.1.1', @structure.value[4].value[0].value)
149
+ assert_nil(@structure.value[4].value[1].value)
150
+ end
151
+
152
+ it "contains the signature itself" do
153
+ to_sign = OpenSSL::ASN1::Set.new(@structure.value[3].value).to_der
154
+ assert_equal(CERTIFICATES.signer_key.sign('SHA256', to_sign), @structure.value[5].value)
155
+ end
156
+
157
+ it "fails if the signature algorithm is not supported" do
158
+ @signed_data.certificate = CERTIFICATES.dsa_signer_certificate
159
+ @signed_data.key = CERTIFICATES.dsa_signer_key
160
+ assert_raises(HexaPDF::Error) { @signed_data.create("data") }
161
+ end
162
+
163
+ it "can use a different digest algorithm" do
164
+ @signed_data.digest_algorithm = 'sha384'
165
+ structure = @signed_data.create("data").value[1].value[4].value[0]
166
+ to_sign = OpenSSL::ASN1::Set.new(structure.value[3].value).to_der
167
+ assert_equal('2.16.840.1.101.3.4.2.2', structure.value[2].value[0].value)
168
+ assert_equal(CERTIFICATES.signer_key.sign('SHA384', to_sign), structure.value[5].value)
169
+ end
170
+
171
+ it "allows delegating the signature to a provided signing block" do
172
+ @signed_data.key = nil
173
+ digest_algorithm = nil
174
+ calculated_hash = nil
175
+ structure = @signed_data.create("data") do |algorithm, hash|
176
+ digest_algorithm = algorithm
177
+ calculated_hash = hash
178
+ "signed"
179
+ end.value[1].value[4].value[0]
180
+ to_sign = OpenSSL::Digest.digest('SHA256', OpenSSL::ASN1::Set.new(structure.value[3].value).to_der)
181
+ assert_equal('sha256', digest_algorithm)
182
+ assert_equal(calculated_hash, to_sign)
183
+ assert_equal('signed', structure.value[5].value)
184
+ end
185
+
186
+ describe "unsigned attributes" do
187
+ it "allows adding a timestamp token" do
188
+ tsh = Object.new
189
+ io = nil
190
+ byte_range = nil
191
+ tsh.define_singleton_method(:sign) do |i_io, i_byte_range|
192
+ io = i_io
193
+ byte_range = i_byte_range
194
+ "timestamp"
195
+ end
196
+ @signed_data.timestamp_handler = tsh
197
+
198
+ structure = @signed_data.create("data").value[1].value[4].value[0]
199
+ assert_equal(structure.value[5].value, io.string)
200
+ assert_equal([0, io.string.size, 0, 0], byte_range)
201
+
202
+ attr = structure.value[6].value.find {|obj| obj.value[0].value == '1.2.840.113549.1.9.16.2.14' }
203
+ assert_equal("timestamp", attr.value[1].value[0])
204
+ end
205
+ end
206
+ end
207
+
208
+ describe "cms signature" do
209
+ it "includes the current time as signing time" do
210
+ Time.stub(:now, Time.at(0)) do
211
+ asn1 = OpenSSL::ASN1.decode(@signed_data.create("data"))
212
+ attr = asn1.value[1].value[0].value[4].value[0].value[3].value.
213
+ find {|obj| obj.value[0].value == 'signingTime' }
214
+ assert_equal(Time.now.utc, attr.value[1].value[0].value)
215
+ end
216
+ end
217
+ end
218
+
219
+ describe "pades signature" do
220
+ it "doesn't include the signing-time attribute" do
221
+ signer_info = @signed_data.create("data", type: :pades).value[1].value[4].value[0]
222
+ refute(signer_info.value[3].value.find {|obj| obj.value[0].value == 'signingTime' })
223
+ end
224
+ end
225
+ end