ruby / openssl

Provides SSL, TLS and general purpose cryptography.
Other
239 stars 162 forks source link

Determining the digest algorithm used by a PKCS#7 object #474

Open gettalong opened 2 years ago

gettalong commented 2 years ago

I'm working on adding digital signature support to HexaPDF, using OpenSSL for the cryptographic needs.

One type of signature is a DER encoded PKCS#7 object. I can successfully extract the signing time, validity periods and other stuff. However, I didn't find any method that returns the message digest algorithm used during signing.

For example, when extracting the PKCS#7 object and looking at it with OpenSSL command line tools, I find that the algorithm is SHA1:

$ openssl pkcs7 -in /tmp/sig.pkcs7 -inform DER -print | head -n 7
PKCS7:
  type: pkcs7-signedData (1.2.840.113549.1.7.2)
  d.sign:
    version: 1
    md_algs:
        algorithm: sha1 (1.3.14.3.2.26)
        parameter: NULL

How can I retrieve this information using Ruby?

rhenium commented 2 years ago

Getting digestAlgorithms (of SignedData = d.sign structure; the one appearing in the excerpt of openssl's output) is currently only possible by manually parsing DER encoded data:

der = File.binread("/tmp/sig.pkcs7")

#    ContentInfo ::= SEQUENCE {
#      contentType ContentType,
#      content
#        [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
p7 = OpenSSL::ASN1.decode(der)
raise "not a signed-data" if p7.value[0].value != "pkcs7-signedData"
signed_data = p7.value.find { |x| x.tag_class == :CONTEXT_SPECIFIC && x.tag == 0 }.value[0]

#    SignedData ::= SEQUENCE {
#      version Version,
#      digestAlgorithms DigestAlgorithmIdentifiers,
#      contentInfo ContentInfo,
#      certificates
#         [0] IMPLICIT ExtendedCertificatesAndCertificates
#           OPTIONAL,
#      crls
#        [1] IMPLICIT CertificateRevocationLists OPTIONAL,
#      signerInfos SignerInfos }
digest_algorithms = signed_data.value[1]

#    DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
#    DigestAlgorithmIdentifier ::= AlgorithmIdentifier
#    AlgorithmIdentifier  ::=  SEQUENCE  {
#         algorithm               OBJECT IDENTIFIER,
#         parameters              ANY DEFINED BY algorithm OPTIONAL  }
digest_algorithms.value.each do |algo_id|
  p algo_id.value[0].value
end

There is also another field indicating the digest algorithm inside SignerInfo. Currently neither has a corresponding method. This would be a nice addition to these classess.

gettalong commented 2 years ago

Thanks @rhenium! If I implement this, would this be needed to be implemented in C or in Ruby?

gettalong commented 2 years ago

@rhenium I was just revisiting this and wondering if you mean by "Currently neither has a corresponding method." that OpenSSL itself has no method for that or that the Ruby interface has no method for that?

I'm asking because I was looking through the available OpenSSL functions at https://www.openssl.org/docs/man3.0/man3/ (to see how a PKCS7/CMS structure could be created manually to include additional signed attributes) and seeing no method for getting the digest algorithm.

alexdean commented 1 year ago

I asked a similar question on stackoverflow. I suspected that getting this info would require digging into the raw ASN1, but wasn't sure. Reading this thread seems to confirm my guess.

There is also another field indicating the digest algorithm inside SignerInfo. Currently neither has a corresponding method. This would be a nice addition to these classess.

@rhenium would you be able to suggest an outline of how you'd like to see this implemented? i'd be available to work on this contribution, but would appreciate some guidance on how to get started.

gettalong commented 1 year ago

@alexdean I did not add this to the Ruby OpenSSL library but in Ruby it would not be that hard, see https://github.com/gettalong/hexapdf/blob/master/lib/hexapdf/digital_signature/cms_handler.rb#L110-L116 where I manually decode the ASN.1 structure.

The drawback is that the structure gets parsed twice, once by Ruby OpenSSL and then manually. So having this integrated in Ruby OpenSSL would certainly be better.

As for your StackOverflow question, maybe this helps: https://github.com/gettalong/hexapdf/blob/master/lib/hexapdf/digital_signature/signing/signed_data_creator.rb (I had a similar problem because I needed to add some custom things to a PKCS#7/CMS signed data structure).