Class: HexaPDF::DigitalSignature::CMSHandler
- Defined in:
- lib/hexapdf/digital_signature/cms_handler.rb
Overview
The signature handler for PKCS#7 a.k.a. CMS signatures. Those include, for example, the adbe.pkcs7.detached and ETSI.CAdES.detached sub-filters.
See: PDF2.0 s12.8.3.3
Instance Attribute Summary
Attributes inherited from Handler
Instance Method Summary collapse
-
#certificate_chain ⇒ Object
Returns the certificate chain.
-
#embedded_tsa_signature ⇒ Object
Returns the OpenSSL::PKCS7 object for the embedded TSA signature if there is one or
nil
otherwise. -
#initialize(signature_dict) ⇒ CMSHandler
constructor
Creates a new signature handler for the given signature dictionary.
-
#signer_certificate ⇒ Object
Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
-
#signer_info ⇒ Object
Returns the signer information object (an instance of OpenSSL::PKCS7::SignerInfo).
-
#signer_name ⇒ Object
Returns the common name of the signer.
-
#signing_time ⇒ Object
Returns the time of signing.
-
#verify(store, allow_self_signed: false) ⇒ Object
Verifies the signature using the provided OpenSSL::X509::Store object.
Constructor Details
#initialize(signature_dict) ⇒ CMSHandler
Creates a new signature handler for the given signature dictionary.
50 51 52 53 |
# File 'lib/hexapdf/digital_signature/cms_handler.rb', line 50 def initialize(signature_dict) super @pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents) end |
Instance Method Details
#certificate_chain ⇒ Object
Returns the certificate chain.
70 71 72 |
# File 'lib/hexapdf/digital_signature/cms_handler.rb', line 70 def certificate_chain @pkcs7.certificates end |
#embedded_tsa_signature ⇒ Object
Returns the OpenSSL::PKCS7 object for the embedded TSA signature if there is one or nil
otherwise.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/hexapdf/digital_signature/cms_handler.rb', line 87 def return @embedded_tsa_signature if defined?(@embedded_tsa_signature) @embedded_tsa_signature = nil p7 = OpenSSL::ASN1.decode(signature_dict.contents.sub(/\x00*\z/, '')) signed_data = p7.value[1].value[0] signer_info = signed_data.value[-1].value[0] # first (and only) signer info return unless signer_info.value[-1].tag == 1 # check for unsigned attributes = signer_info.value[-1].value.find do |unsigned_attr| unsigned_attr.value[0].value == "id-smime-aa-timeStampToken" end return unless @embedded_tsa_signature = OpenSSL::PKCS7.new(.value[1].value[0]) end |
#signer_certificate ⇒ Object
Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
75 76 77 78 |
# File 'lib/hexapdf/digital_signature/cms_handler.rb', line 75 def signer_certificate info = signer_info certificate_chain.find {|cert| cert.issuer == info.issuer && cert.serial == info.serial } end |
#signer_info ⇒ Object
Returns the signer information object (an instance of OpenSSL::PKCS7::SignerInfo).
81 82 83 |
# File 'lib/hexapdf/digital_signature/cms_handler.rb', line 81 def signer_info @pkcs7.signers.first end |
#signer_name ⇒ Object
Returns the common name of the signer.
56 57 58 |
# File 'lib/hexapdf/digital_signature/cms_handler.rb', line 56 def signer_name signer_certificate.subject.to_a.assoc("CN")&.[](1) || super end |
#signing_time ⇒ Object
Returns the time of signing.
61 62 63 64 65 66 67 |
# File 'lib/hexapdf/digital_signature/cms_handler.rb', line 61 def signing_time if .signers.first.signed_time else signer_info.signed_time rescue super end end |
#verify(store, allow_self_signed: false) ⇒ Object
Verifies the signature using the provided OpenSSL::X509::Store object.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/hexapdf/digital_signature/cms_handler.rb', line 103 def verify(store, allow_self_signed: false) result = super signer_info = self.signer_info signer_certificate = self.signer_certificate certificate_chain = self.certificate_chain if certificate_chain.empty? result.log(:error, "No certificates found in signature") return result end if @pkcs7.signers.size != 1 result.log(:error, "Exactly one signer needed, found #{@pkcs7.signers.size}") end unless signer_certificate result.log(:error, "Signer serial=#{signer_info.serial} issuer=#{signer_info.issuer} " \ "not found in certificates stored in PKCS7 object") return result end if result.log(:info, 'Signing time comes from timestamp authority') end key_usage = signer_certificate.extensions.find {|ext| ext.oid == 'keyUsage' } key_usage = key_usage&.value&.split(', ') if key_usage&.include?("Non Repudiation") && !key_usage.include?("Digital Signature") result.log(:info, 'Certificate used for non-repudiation') elsif !key_usage || !key_usage.include?("Digital Signature") result.log(:error, "Certificate key usage is missing 'Digital Signature' or 'Non Repudiation'") end if signature_dict.signature_type == 'ETSI.RFC3161' # Getting the needed values is not directly supported by Ruby OpenSSL p7 = OpenSSL::ASN1.decode(signature_dict.contents.sub(/\x00*\z/, '')) signed_data = p7.value[1].value[0] content_info = signed_data.value[2] content = OpenSSL::ASN1.decode(content_info.value[1].value[0].value) digest_algorithm = content.value[2].value[0].value[0].value original_hash = content.value[2].value[1].value recomputed_hash = OpenSSL::Digest.digest(digest_algorithm, signature_dict.signed_data) hash_valid = (original_hash == recomputed_hash) else data = signature_dict.signed_data hash_valid = true # hash will be checked by @pkcs7.verify end if hash_valid && @pkcs7.verify(certificate_chain, store, data, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY) result.log(:info, "Signature valid") else result.log(:error, "Signature verification failed") end certs = [signer_certificate] cur_cert = certs.first while true cur_cert = certificate_chain.find {|cert| cert.subject == cur_cert.issuer } if cur_cert && !certs.include?(cur_cert) certs << cur_cert else break end end cert_subjects = certs.map {|cert| cert.subject.to_a.assoc("CN")&.[](1) } result.log(:info, "Certificate chain: #{cert_subjects.join(" -> ")}") result end |