alexdean / as2

AS2 protocol implementation in Ruby
https://rubygems.org/gems/as2
MIT License
3 stars 2 forks source link

cleaner method for determining micalg in format v1 MDNs #26

Open alexdean opened 1 year ago

alexdean commented 1 year ago

in #25 i added a new MDN format which does not use OpenSSL::PKCS7.write_smime.

One item i wasn't able to solve in that PR was how to specify the micalg parameter used in the Content-Type header. The workaround in that PR is to still call write_smime and use a regex to extract the micalg parameter. Would like to remove that hack when possible.

what we need to replicate

write_smime produces a header like this:

Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha-256"; boundary="--- FA974EDD77D0E708FEB7C0F3E6B14F13"

but i have not been able to figure out where/how it chooses the sha-256 value. it is unconnected to the certificate we use or the MIC alg we use in the MDN report we're signing.

looking for input

asked for help in https://stackoverflow.com/questions/75934159/how-does-openssl-smime-determine-micalg-parameter should check back there periodically to look for an answer.

alexdean commented 1 year ago

also tried an approach based on code cited in https://stackoverflow.com/questions/53044007/how-to-use-sha1-digest-during-signing-with-opensslpkcs7-sign-when-creating-smi

micalg = 'sha-256'

# construct the signature
pkcs7 = OpenSSL::PKCS7.new
pkcs7.type = :signed
signer = OpenSSL::PKCS7::Signer.new(
         @server_info.certificate,
         @server_info.pkey,
         As2::DigestSelector.for_code(micalg).new
       )
pkcs7.add_data(mdn_text)
pkcs7.add_certificate(@server_info.certificate)
pkcs7.add_signer(signer)
pkcs7.detached = true

File.open('test_sig.p7m', 'wb') { |fp| fp.write(pkcs7.to_pem) }
File.open('test_sig_message.txt', 'wb') { |fp| fp.write(mdn_text) }

This signature is missing some important data which prevents it from being verified. Note unable to find message digest in the verification error at the bottom.

-> % openssl smime -verify -binary -inform PEM \
-in test_sig.p7m \
-content test_sig_message.txt \
-certfile test/certificates/server.crt \
-nointern \
-noverify

Content-Type: multipart/report; report-type=disposition-notification; 
    boundary="----=_Part_7_20230405102219370"

------=_Part_7_20230405102219370
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

The AS2 message has been received successfully

------=_Part_7_20230405102219370
Content-Type: message/disposition-notification
Content-Transfer-Encoding: 7bit

Reporting-UA: BOB
Original-Recipient: rfc822; BOB
Final-Recipient: rfc822; BOB
Original-Message-ID: <message@server>
Disposition: automatic-action/MDN-sent-automatically; processed
Received-Content-MIC: micmicmic, sha256

------=_Part_7_20230405102219370--

Verification failure
140704403170880:error:21FFF06C:PKCS7 routines:CRYPTO_internal:unable to find message digest:/AppleInternal/Library/BuildRoots/9e200cfa-7d96-11ed-886f-a23c4f261b56/Library/Caches/com.apple.xbs/Sources/libressl/libressl-3.3/crypto/pkcs7/pk7_doit.c:1036:
140704403170880:error:21FFF069:PKCS7 routines:CRYPTO_internal:signature failure:/AppleInternal/Library/BuildRoots/9e200cfa-7d96-11ed-886f-a23c4f261b56/Library/Caches/com.apple.xbs/Sources/libressl/libressl-3.3/crypto/pkcs7/pk7_smime.c:407:
alexdean commented 1 year ago

comparing this signature with one generated by OpenSSL::PKCS7.sign, it's easy to spot differences.

our generated signature

-> % openssl pkcs7 -in test_sig.p7m -print  

    signer_info:
        version: 1
        issuer_and_serial: 
          issuer: O=Ruby AS2 Test Server, CN=server.test-ruby-as2.com
          serial: 10029315826167849756
        digest_alg: 
          algorithm: sha256 (2.16.840.1.101.3.4.2.1)
          parameter: NULL
        auth_attr:
            object: contentType (1.2.840.113549.1.9.3)
            value.set:
              OBJECT:pkcs7-data (1.2.840.113549.1.7.1)
        digest_enc_alg: 
          algorithm: rsaEncryption (1.2.840.113549.1.1.1)
          parameter: NULL
        enc_digest: 
        unauth_attr:
          <EMPTY>
-----BEGIN PKCS7-----

compare this to a signature created by OpenSSL::PKCS7.sign and then dumped to a PEM file...

    signer_info:
        version: 1
        issuer_and_serial: 
          issuer: C=US, CN=ruby-as2.test.com
          serial: 10470737887412000493
        digest_alg: 
          algorithm: sha256 (2.16.840.1.101.3.4.2.1)
          parameter: NULL
        auth_attr:
            object: contentType (1.2.840.113549.1.9.3)
            value.set:
              OBJECT:pkcs7-data (1.2.840.113549.1.7.1)

            object: signingTime (1.2.840.113549.1.9.5)
            value.set:
              UTCTIME:Apr  4 20:25:53 2023 GMT

            object: messageDigest (1.2.840.113549.1.9.4)
            value.set:
              OCTET STRING:
                0000 - e3 c5 d4 00 e9 47 6d 40-cc 32 26 40 d3   .....Gm@.2&@.
                000d - b9 03 7e e4 7d d9 70 ab-ab 99 be 56 c7   ..~.}.p....V.
                001a - 61 d7 0a a9 5b ba                        a...[.

            object: S/MIME Capabilities (1.2.840.113549.1.9.15)
            value.set:
              SEQUENCE:
    0:d=0  hl=2 l= 106 cons: SEQUENCE          
    2:d=1  hl=2 l=  11 cons:  SEQUENCE          
    4:d=2  hl=2 l=   9 prim:   OBJECT            :aes-256-cbc
   15:d=1  hl=2 l=  11 cons:  SEQUENCE          
   17:d=2  hl=2 l=   9 prim:   OBJECT            :aes-192-cbc
   28:d=1  hl=2 l=  11 cons:  SEQUENCE          
   30:d=2  hl=2 l=   9 prim:   OBJECT            :aes-128-cbc
   41:d=1  hl=2 l=  10 cons:  SEQUENCE          
   43:d=2  hl=2 l=   8 prim:   OBJECT            :des-ede3-cbc
   53:d=1  hl=2 l=  14 cons:  SEQUENCE          
   55:d=2  hl=2 l=   8 prim:   OBJECT            :rc2-cbc
   65:d=2  hl=2 l=   2 prim:   INTEGER           :80
   69:d=1  hl=2 l=  13 cons:  SEQUENCE          
   71:d=2  hl=2 l=   8 prim:   OBJECT            :rc2-cbc
   81:d=2  hl=2 l=   1 prim:   INTEGER           :40
   84:d=1  hl=2 l=   7 cons:  SEQUENCE          
   86:d=2  hl=2 l=   5 prim:   OBJECT            :des-cbc
   93:d=1  hl=2 l=  13 cons:  SEQUENCE          
   95:d=2  hl=2 l=   8 prim:   OBJECT            :rc2-cbc
  105:d=2  hl=2 l=   1 prim:   INTEGER           :28
        digest_enc_alg: 
          algorithm: rsaEncryption (1.2.840.113549.1.1.1)
          parameter: NULL
        enc_digest: 
          0000 - 6b 19 21 cd b4 60 90 68-43 89 2b 0a 09 28 4c   k.!..`.hC.+..(L
          000f - c7 1a 70 b5 39 4a 44 ca-2a 11 37 4d 91 78 94   ..p.9JD.*.7M.x.
          001e - be 66 3c e4 1d 9f c4 b2-58 9d b6 42 4a d3 1a   .f<.....X..BJ..
          002d - aa 5e 87 77 8c f7 ab a6-90 31 e2 03 63 69 0a   .^.w.....1..ci.
          003c - f0 54 ba 36 38 88 56 4c-36 77 4a 90 ab e6 04   .T.68.VL6wJ....
          004b - 3d 12 79 63 14 8b 20 26-42 0f bf fa 3f 87 62   =.yc.. &B...?.b
          005a - a3 35 bc 23 7c c3 3f b5-28 96 36 28 9b 5c d9   .5.#|.?.(.6(.\.
          0069 - e3 3c 52 70 f4 aa 1d 9f-6c 21 ff 26 21 1a 94   .<Rp....l!.&!..
          0078 - 9b a0 d7 93 04 51 9c 28-f8 5f 2f a0 e7 7d 67   .....Q.(._/..}g
          0087 - 65 df 15 49 2a d3 d1 02-a5 69 a4 b2 d8 27 68   e..I*....i...'h
          0096 - e1 38 81 21 c2 91 fa 15-8f ba 66 7f c2 56 82   .8.!......f..V.
          00a5 - 4c d0 02 1d 7f 09 e4 97-6a 2b 7a 43 b3 84 0a   L.......j+zC...
          00b4 - 55 fe a7 fa e7 9c 9a 34-5a cb 98 16 a3 53 15   U......4Z....S.
          00c3 - 04 ec 3c 43 37 c4 0e f0-c2 26 65 84 1f c6 a2   ..<C7....&e....
          00d2 - a0 08 dd 9d 70 0d 7e ee-53 44 b6 b0 07 00 1d   ....p.~.SD.....
          00e1 - 1b 67 cd 0b 4b f0 90 3a-d1 27 3a 47 47 a2 b2   .g..K..:.':GG..
          00f0 - cd 97 78 2c c7 70 97 8a-83 b6 5e 82 47 f5 e8   ..x,.p....^.G..
          00ff - 42                                             B
        unauth_attr:
          <EMPTY>
-----BEGIN PKCS7-----
alexdean commented 1 year ago

see also https://github.com/ruby/openssl/issues/474

alexdean commented 1 year ago

sample code to read digest algorithm...

asn1 decoding method also used in #29.

def build_concise_asn1(item)
  if item.respond_to?(:value)
    item_value = item.value
  else
    item_value = item
  end

  if item.respond_to?(:each)
    out_value = []
    # OpenSSL::ASN1::Sequence responds to .each
    item.each { |i| out_value << build_concise_asn1(i) }
  elsif item_value.respond_to?(:each)
    out_value = []
    # OpenSSL::ASN1::ASN1Data does not respond to .each
    # but it's .value may be an array so we should recurse
    item_value.each { |i| out_value << build_concise_asn1(i) }
  else
    # when we hit a leaf node
    if item.is_a?(OpenSSL::ASN1::Integer)
      out_value = item_value.to_i
    elsif item.is_a?(OpenSSL::ASN1::ObjectId)
      out_value = {oid:item.oid, value:item_value}
    else
      out_value = item_value
    end
  end

  out_value
end

pkcs7 = OpenSSL::PKCS7.sign(@server_info.certificate, @server_info.pkey, mdn_text)
pkcs7.detached = true
asn1 = OpenSSL::ASN1.decode(pkcs7)
simpler = build_concise_asn1(asn1)

simpler[1][0][1].map { |alg| alg[0][:value] }.join(',')
=> "SHA256"

things to check/verify:

  1. using join because iirc multiple algorithms can be specified. need to review ASN1 schema to confirm if this is accurate.

references

https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/
http://luca.ntop.org/Teaching/Appunti/asn1.html