# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) class OpenSSL::TestPKCS7 < OpenSSL::TestCase def setup super @rsa1024 = Fixtures.pkey("rsa1024") @rsa2048 = Fixtures.pkey("rsa2048") ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1") ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2") ca_exts = [ ["basicConstraints","CA:TRUE",true], ["keyUsage","keyCertSign, cRLSign",true], ["subjectKeyIdentifier","hash",false], ["authorityKeyIdentifier","keyid:always",false], ] @ca_cert = issue_cert(ca, @rsa2048, 1, ca_exts, nil, nil) ee_exts = [ ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true], ["authorityKeyIdentifier","keyid:always",false], ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false], ] @ee1_cert = issue_cert(ee1, @rsa1024, 2, ee_exts, @ca_cert, @rsa2048) @ee2_cert = issue_cert(ee2, @rsa1024, 3, ee_exts, @ca_cert, @rsa2048) end def test_signed store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) ca_certs = [@ca_cert] data = "aaaaa\r\nbbbbb\r\nccccc\r\n" tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs) p7 = OpenSSL::PKCS7.new(tmp.to_der) certs = p7.certificates signers = p7.signers assert(p7.verify([], store)) assert_equal(data, p7.data) assert_equal(2, certs.size) assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) assert_equal(1, signers.size) assert_equal(@ee1_cert.serial, signers[0].serial) assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) # AWS-LC does not generate authenticatedAttributes assert_in_delta(Time.now, signers[0].signed_time, 10) unless aws_lc? # Normally OpenSSL tries to translate the supplied content into canonical # MIME format (e.g. a newline character is converted into CR+LF). # If the content is a binary, PKCS7::BINARY flag should be used. data = "aaaaa\nbbbbb\nccccc\n" flag = OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::NOATTR tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag) p7 = OpenSSL::PKCS7.new(tmp.to_der) certs = p7.certificates signers = p7.signers assert(p7.verify([], store)) assert_equal(data, p7.data) assert_equal(2, certs.size) assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) assert_equal(1, signers.size) assert_equal(@ee1_cert.serial, signers[0].serial) assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) assert_raise(OpenSSL::PKCS7::PKCS7Error) { signers[0].signed_time } # A signed-data which have multiple signatures can be created # through the following steps. # 1. create two signed-data # 2. copy signerInfo and certificate from one to another tmp1 = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, [], flag) tmp2 = OpenSSL::PKCS7.sign(@ee2_cert, @rsa1024, data, [], flag) tmp1.add_signer(tmp2.signers[0]) tmp1.add_certificate(@ee2_cert) p7 = OpenSSL::PKCS7.new(tmp1.to_der) certs = p7.certificates signers = p7.signers assert(p7.verify([], store)) assert_equal(data, p7.data) assert_equal(2, certs.size) assert_equal(2, signers.size) assert_equal(@ee1_cert.serial, signers[0].serial) assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) assert_equal(@ee2_cert.serial, signers[1].serial) assert_equal(@ee2_cert.issuer.to_s, signers[1].issuer.to_s) end def test_signed_add_signer data = "aaaaa\nbbbbb\nccccc\n" psi = OpenSSL::PKCS7::SignerInfo.new(@ee1_cert, @rsa1024, "sha256") p7 = OpenSSL::PKCS7.new p7.type = :signed p7.add_signer(psi) p7.add_certificate(@ee1_cert) p7.add_certificate(@ca_cert) p7.add_data(data) store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) assert_equal(true, p7.verify([], store)) assert_equal(true, OpenSSL::PKCS7.new(p7.to_der).verify([], store)) assert_equal(1, p7.signers.size) end def test_detached_sign store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) ca_certs = [@ca_cert] data = "aaaaa\nbbbbb\nccccc\n" flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag) p7 = OpenSSL::PKCS7.new(tmp.to_der) assert_nothing_raised do OpenSSL::ASN1.decode(p7) end certs = p7.certificates signers = p7.signers assert(!p7.verify([], store)) assert(p7.verify([], store, data)) assert_equal(data, p7.data) assert_equal(2, certs.size) assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) assert_equal(1, signers.size) assert_equal(@ee1_cert.serial, signers[0].serial) assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) end def test_signed_authenticated_attributes # Using static PEM data because AWS-LC does not support generating one # with authenticatedAttributes. # # p7 was generated with OpenSSL 3.4.1 with this program with commandline # "faketime 2025-04-03Z ruby prog.rb": # # require_relative "test/openssl/utils" # include OpenSSL::TestUtils # key = Fixtures.pkey("p256") # cert = issue_cert(OpenSSL::X509::Name.new([["CN", "cert"]]), key, 1, [], nil, nil) # p7 = OpenSSL::PKCS7.sign(cert, key, "content", []) # puts p7.to_pem p7 = OpenSSL::PKCS7.new(<<~EOF) -----BEGIN PKCS7----- MIICvgYJKoZIhvcNAQcCoIICrzCCAqsCAQExDzANBglghkgBZQMEAgEFADAWBgkq hkiG9w0BBwGgCQQHY29udGVudKCCAQ4wggEKMIGxoAMCAQICAQEwCgYIKoZIzj0E AwIwDzENMAsGA1UEAwwEY2VydDAeFw0yNTA0MDIyMzAwMDFaFw0yNTA0MDMwMTAw MDFaMA8xDTALBgNVBAMMBGNlcnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQW CWTZz6hVQgpDrh5kb1uEs09YHuVJn8CsrjV4bLnADNT/QbnVe20J4FSX4xqFm2f1 87Ukp0XiomZLf11eekQ2MAoGCCqGSM49BAMCA0gAMEUCIEg1fDI8b3hZAArgniVk HeM6puwgcMh5NXwvJ9x0unVmAiEAppecVTSQ+yEPyBG415Og6sK+RC78pcByEC81 C/QSwRYxggFpMIIBZQIBATAUMA8xDTALBgNVBAMMBGNlcnQCAQEwDQYJYIZIAWUD BAIBBQCggeQwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx DxcNMjUwNDAzMDAwMDAxWjAvBgkqhkiG9w0BCQQxIgQg7XACtDnprIRfIjV9gius FERzD722AW0+yUMil7nsn3MweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASow CwYJYIZIAWUDBAEWMAsGCWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0D AgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwCgYI KoZIzj0EAwIESDBGAiEAssymc28HySAhg+XeWIpSbtzkwycr2JG6dzHRZ+vn0ocC IQCJVpo1FTLZOHSc9UpjS+VKR4cg50Iz0HiPyo6hwjCrwA== -----END PKCS7----- EOF cert = p7.certificates[0] store = OpenSSL::X509::Store.new.tap { |store| store.time = Time.utc(2025, 4, 3) store.add_cert(cert) } assert_equal(true, p7.verify([], store)) assert_equal(1, p7.signers.size) signer = p7.signers[0] assert_in_delta(Time.utc(2025, 4, 3), signer.signed_time, 10) end def test_enveloped certs = [@ee1_cert, @ee2_cert] cipher = OpenSSL::Cipher::AES.new("128-CBC") data = "aaaaa\nbbbbb\nccccc\n" tmp = OpenSSL::PKCS7.encrypt(certs, data, cipher, OpenSSL::PKCS7::BINARY) p7 = OpenSSL::PKCS7.new(tmp.to_der) recip = p7.recipients assert_equal(:enveloped, p7.type) assert_equal(2, recip.size) assert_equal(@ca_cert.subject.to_s, recip[0].issuer.to_s) assert_equal(2, recip[0].serial) assert_equal(data, p7.decrypt(@rsa1024, @ee1_cert)) assert_equal(@ca_cert.subject.to_s, recip[1].issuer.to_s) assert_equal(3, recip[1].serial) assert_equal(data, p7.decrypt(@rsa1024, @ee2_cert)) assert_equal(data, p7.decrypt(@rsa1024)) # Default cipher has been removed in v3.3 assert_raise_with_message(ArgumentError, /RC2-40-CBC/) { OpenSSL::PKCS7.encrypt(certs, data) } end def test_data asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("pkcs7-data"), OpenSSL::ASN1::OctetString("content", 0, :EXPLICIT), ]) p7 = OpenSSL::PKCS7.new p7.type = :data p7.data = "content" assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.add_certificate(@ee1_cert) } assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.certificates = [@ee1_cert] } assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.cipher = "aes-128-cbc" } assert_equal(asn1.to_der, p7.to_der) p7 = OpenSSL::PKCS7.new(asn1) assert_equal(:data, p7.type) assert_equal(false, p7.detached?) # Not applicable assert_nil(p7.certificates) assert_nil(p7.crls) # Not applicable. Should they return nil or raise an exception instead? assert_equal([], p7.signers) assert_equal([], p7.recipients) # PKCS7#verify can't distinguish verification failure and other errors store = OpenSSL::X509::Store.new assert_equal(false, p7.verify([@ee1_cert], store)) assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.decrypt(@rsa1024) } end def test_empty_signed_data_ruby_bug_19974 data = "-----BEGIN PKCS7-----\nMAsGCSqGSIb3DQEHAg==\n-----END PKCS7-----\n" assert_raise(ArgumentError) { OpenSSL::PKCS7.new(data) } data = <