Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patch and enable tests with AWS-LC #855

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

samuel40791765
Copy link
Contributor

Second follow up from #833

This contribution should be the next and final phase for adding support for AWS-LC in the ruby/openssl gem. These changes represent the build portion of a larger patch in the AWS-LC's Ruby integration CI. With this change, all tests have been accounted for and reenabled in the CI.

I understand that this patch is a bit large, so I've broken everything up to into smaller documented commits that'll make things easier to digest. I've also tried following a similar commit messaging pattern that the maintainers use.
However, do feel free to let me know if I should make separate PR contributions for some of these or if there are any changes within the commit messages that I should adjust. Thank you!!

test/openssl/test_ssl.rb Outdated Show resolved Hide resolved
@@ -28,7 +28,7 @@ def test_new_break_on_non_fips
end

def test_new_break_on_fips
omit_on_non_fips
omit_on_non_fips or return aws_lc? # This behavior only applies to OpenSSL.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is equivalent to if !omit_on_non_fips; return aws_lc? end which doesn't really make sense. I think you want return if aws_lc? (or return unless openssl?) on a separate line.

I'm not entirely sure if this test is useful to have in ruby/openssl's tests at all. OpenSSL's fips provider skipping generation and returning a pre-defined group sounds like an implementation detail to me. @junaruga What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll do return unless openssl? for now. Agreed that this is probably a new implementation detail specific to OpenSSL FIPS, most libcrypto's normally don't deviate from the original behavior.

test/openssl/test_asn1.rb Outdated Show resolved Hide resolved
test/openssl/test_bn.rb Outdated Show resolved Hide resolved
test/openssl/test_fips.rb Outdated Show resolved Hide resolved
test/openssl/test_pkcs12.rb Outdated Show resolved Hide resolved
assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result)
# OpenSSL and LibreSSL return V_ERR_SELF_SIGNED_CERT_IN_CHAIN as the error code.
# AWS-LC returns V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY as the error code.
assert_includes [OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY], ssl.verify_result
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might be getting inconsistent results because the certificates are not conformant to RFC 5280. We should instead fix the certificates (@ca_cert and @svr_cert in utils.rb) so that they include SKI/AKI extensions.

     ca_exts = [
       ["basicConstraints","CA:TRUE",true],
       ["keyUsage","cRLSign,keyCertSign",true],
+      ["subjectKeyIdentifier","hash",false],
     ]
     ee_exts = [
       ["keyUsage","keyEncipherment,digitalSignature",true],
+      ["authorityKeyIdentifier","issuer:always,keyid:always",false],
     ]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave this a shot, but this doesn't resolve the issue :(. Unfortunately, I think this has more to do with the fact that AWS-LC's certificate verification logic has not been updated to align with later versions of OpenSSL. We plan to make this a better experience in the near future, but completing the work will take time. I'll also update the commit message to mention certificate verification logic differences to make things clearer.
I found a similar issue with LibreSSL in the same test that was fixed a while ago: a9954ba. The same assertion originally existed for LibreSSL, so I've changed the assertion to match the original format.

That being said, I can add the certificate improvements within the same commit if you'd like us to? Although it does seem different from the certificate verification logic issues this is trying to work around though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it seems like a different issue.

I think the actual cause might be that SSL_MODE_NO_AUTO_CHAIN is turned on by default in AWS-LC, unlike OpenSSL: https://github.com/aws/aws-lc/blob/154f9986bc18f8f0e6126157a3abc84230456f77/include/openssl/ssl.h#L844-L861

ssl.peer_cert_chain on the client SSLSocket shows 2 certificates with OpenSSL and just 1 with AWS-LC, which explains the different error codes.

I don't think start_server should depend on this behavior, and it doesn't seem intentional according to what I can see in git log in ruby/ruby's tree. I'll make a separate PR to fix this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#858 removed the cert store from start_server which was triggering the different behavior.

test_verify_result and test_connect_certificate_verify_failed_exception_message should not need to relax the assertions.

test/openssl/test_ssl_session.rb Outdated Show resolved Hide resolved
test/openssl/test_pkey_dsa.rb Outdated Show resolved Hide resolved
test/openssl/test_pkey_dh.rb Outdated Show resolved Hide resolved
# definite-length DER OCTET_STRING whereas upstream (i.e.
# OpenSSL) encodes EncryptedContent as indefinite-length
# BER OCTET_STRING. The discrepancy is due to AWS-LC's lack
# of support for indefinite OCTET_STRINGS.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this description is not accurate. The input (pki_msg.data) uses indefinite length encodings, and OpenSSL re-encodes it into DER. AWS-LC's output is also DER, but the content is incorrect. If this is expected, I'd suggest skipping this test case for now.

Here is the inner PKCS#7 (pki_msg.data), formatted to PEM:

-----BEGIN PKCS7-----
MIAGCSqGSIb3DQEHA6CAMIACAQAxggEQMIIBDAIBADB1MHAxEDAOBgNVBAoMB2V4
YW1wbGUxFzAVBgNVBAMMDlRBUk1BQyBST09UIENBMSIwIAYJKoZIhvcNAQkBFhNz
b21lb25lQGV4YW1wbGUub3JnMQswCQYDVQQGEwJVUzESMBAGA1UEBwwJVG93biBI
YWxsAgFmMA0GCSqGSIb3DQEBAQUABIGAbKV17HvGYRtRRBNz1QLpW763UedhVj5K
Xi70o4BJGM04lItAgt6aFC9SruZjpWr1gCYKCaRSAg273DeGTQwsDoZ86CPXzBpp
tYLz0MteQXYYWUaPZT+xmvx4NgDyk9P9MoT7JifsPrtXuzqCRFXhGdu8d/ru+OWx
hHLvKH+bYekwgAYJKoZIhvcNAQcBMBQGCCqGSIb3DQMHBAgTbNlOZjLHf6CABIIC
EFOnLq/EAc9Nv+HjKR3ZVPSJMq0TImjGf5Mvc3nDgI572Hdo2aku0YXM6WjSWkpY
txpg7Cqxfl6hPSefLPUnBqlIoM2qbrE7MSKEVD6+2bW9GqYPFVg4qQLLsOxnxJIM
fOvLFfd7guL+iLH424XfiUUxaf8EdZE4u2IEl4REvkS1FoEGwyA4BEGMSeVPedQC
bZ0qY7Pc2tmZE3XfEUhIsyStG0Nb6i6AKcAFYGapbgE6kAB0gwsYcHlWMOvsvdAf
cTq6jwtHlO1s68qtvkWquTQ9lpX+fzddUUNxEHSqv5eU3oo6fT3Vj5ZFIVlaA5Th
ZMrI5PgRPuwJM4GL8/VLwY5mbDLFqn/irGeEvP99J3S87ornLLunjpxSy1/AymcV
ep2H32Tj82WS/IRQXBOzz4EnQRJGszKxAV6tY+Zje3sWyTTgObhlsiTQTDgnvtSW
8RvVHqKrwgkxxEsRHg7u8UdzZ0jg+O5+3F8B6/NWMyts0OaFqT9wvI8yO7VIy3dU
tGdz7Hde6Ggp/iTn1LbgdJ3N8Hzxf1j6NMWUKHVsadvwpRJbUeqq9c3+QuxsJi8w
WemxxQCE+tPyc1dP+ej5/M7bERbSOHMGgX03758IvP7A/fy2DjGPv2+lAwlEke0U
ze1367QKgxM0nc3SZDlptY7zPIJC5saWXb8Rt2bw2JxEBOTavrp+ZwJ8tcH961on
qwQIxOZ7YgJoLOQAAAAAAAAAAAAA
-----END PKCS7-----

Here is the EncryptedContentInfo part in the der2ascii output. It does show an indefinite length constructed encoding for EncryptedContent (OCTET STRING), which is implicit tagged [0].

      SEQUENCE indefinite {
        # data
        OBJECT_IDENTIFIER { 1.2.840.113549.1.7.1 }
        SEQUENCE {
          # DES-EDE3-CBC
          OBJECT_IDENTIFIER { 1.2.840.113549.3.7 }
          OCTET_STRING { `136cd94e6632c77f` }
        }
        [0] indefinite {
          OCTET_STRING { `53a72[...snip]` }
          OCTET_STRING { `c4e67b6202682ce4` }
        }
      }

In OpenSSL output (pki_message_content_pem), EncryptedContent uses an equivalent primitive encoding:

      SEQUENCE {
        # data
        OBJECT_IDENTIFIER { 1.2.840.113549.1.7.1 }
        SEQUENCE {
          # DES-EDE3-CBC
          OBJECT_IDENTIFIER { 1.2.840.113549.3.7 }
          OCTET_STRING { `136cd94e6632c77f` }
        }
        [0 PRIMITIVE] { `53a72[...snip...]2ce4` }
      }

In AWS-LC output (pki_message_content_pem_awslc), EncryptedContent also uses a primitive encoding, but the content contains extra OCTET STRING headers in it:

      SEQUENCE {
        # data
        OBJECT_IDENTIFIER { 1.2.840.113549.1.7.1 }
        SEQUENCE {
          # DES-EDE3-CBC
          OBJECT_IDENTIFIER { 1.2.840.113549.3.7 }
          OCTET_STRING { `136cd94e6632c77f` }
        }
        [0 PRIMITIVE] {
          OCTET_STRING { `53a72[...snip]` }
          OCTET_STRING { `c4e67b6202682ce4` }
        }
      }

I took a look at AWS-LC's d2i_PKCS7() and this part stood out to me: https://github.com/aws/aws-lc/blob/7518c784f4d6a8933345d9192b82ecc19bea4403/crypto/pkcs7/pkcs7_asn1.c#L45-L49

I haven't dug deeper yet, but CBS_asn1_ber_to_der() not taking any contextual information seems odd, as correct conversion of structures like this would require it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great points and indeed there seems to be something we overlooked here. I'll sync up internally with our team to figure how to properly adjust this. Thanks!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done some deeper diving and I've pinned the reason down to AWS-LC's lack of support for ASN1_OCTET_STRING_NDEF and the ASN1_TFLG_NDEF flag. These act as a helper to request indefinite-length encoding for the ASN1 encoding functions in OpenSSL. This was removed from AWS-LC in this commit: aws/aws-lc@45858ae, and there's a mention of OpenSSL using this for their PKCS7 implementation within the commit message.

AWS-LC and OpenSSL both use the same generic ASN.1 functions which depend on the ASN.1 definition macros defined in each library. There's a slight difference in our definitions that I discovered here:

AWS-LC doesn't support the aforementioned ASN1_OCTET_STRING_NDEF tag that OpenSSL uses for PKCS7 and we decided to explicitly tag the type as ASN1_OCTET_STRING instead. This helps explain the additional OCTET STRING headers you're seeing with the translated ASN1 contents for AWS-LC.

I was looking through RFC2315's definition of EncryptedContentInfo to double check and it does explicitly call out that the EncryptedContent field should be an OCTET STRING. It's also mentioned later on in section 10.3 that:

Definite-length encoding is more appropriate for simple types such as octet strings, so definite-length encoding is chosen.

Despite the slight difference, we think both OpenSSL and AWS-LC have valid ASN1 representations, just one has the explicit OCTET STRING header, while OpenSSL doesn't.

I really appreciate the help with this. I'll patch the test to be skipped with AWS-LC for the time being and document the actual root cause more accurately.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite the slight difference, we think both OpenSSL and AWS-LC have valid ASN1 representations, just one has the explicit OCTET STRING header, while OpenSSL doesn't.

I'm afraid you are mixing something up here. Both outputs are well formed DER (then formatted to PEM), but they are representations of different data. The contents octets of EncryptedContent in AWS-LC's start with 04 82 02 10 53 a7 ... instead of the correct 53 a7 ....

Ideally AWS-LC should either produce the identical output as OpenSSL, since DER is deterministic, or make d2i_PKCS7{,_bio}() return an error if such an input is not supported.

Anyway, skipping it in ruby/openssl's test suite seems reasonable for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverting aws/aws-lc@2a72226 makes d2i_PKCS7{,_bio}() report an error when it encounters the constructed encoding of octet string (EncryptedContent).

I think reintroducing support for parsing constructed encoding of strings might require reverting this commit (perhaps excluding the part related to indefinite lengths): aws/aws-lc@a70edd4

AWS-LC's FIPS mode is decided at compile time. FIPS in AWS-LC can't be
toggled on and off like OpenSSL, so tests that attempt to do so are
incompatible.
Copy link
Contributor Author

@samuel40791765 samuel40791765 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks the review! We're still discussing a path forward on the PKCS7 comment/issue, but I've attempted to address the rest of your comments.

test/openssl/test_fips.rb Outdated Show resolved Hide resolved
test/openssl/test_pkcs12.rb Outdated Show resolved Hide resolved
test/openssl/test_pkey_dsa.rb Outdated Show resolved Hide resolved
@@ -28,7 +28,7 @@ def test_new_break_on_non_fips
end

def test_new_break_on_fips
omit_on_non_fips
omit_on_non_fips or return aws_lc? # This behavior only applies to OpenSSL.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll do return unless openssl? for now. Agreed that this is probably a new implementation detail specific to OpenSSL FIPS, most libcrypto's normally don't deviate from the original behavior.

test/openssl/test_ssl_session.rb Outdated Show resolved Hide resolved
test/openssl/test_ssl.rb Outdated Show resolved Hide resolved
assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result)
# OpenSSL and LibreSSL return V_ERR_SELF_SIGNED_CERT_IN_CHAIN as the error code.
# AWS-LC returns V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY as the error code.
assert_includes [OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY], ssl.verify_result
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave this a shot, but this doesn't resolve the issue :(. Unfortunately, I think this has more to do with the fact that AWS-LC's certificate verification logic has not been updated to align with later versions of OpenSSL. We plan to make this a better experience in the near future, but completing the work will take time. I'll also update the commit message to mention certificate verification logic differences to make things clearer.
I found a similar issue with LibreSSL in the same test that was fixed a while ago: a9954ba. The same assertion originally existed for LibreSSL, so I've changed the assertion to match the original format.

That being said, I can add the certificate improvements within the same commit if you'd like us to? Although it does seem different from the certificate verification logic issues this is trying to work around though.

test/openssl/test_asn1.rb Outdated Show resolved Hide resolved
# definite-length DER OCTET_STRING whereas upstream (i.e.
# OpenSSL) encodes EncryptedContent as indefinite-length
# BER OCTET_STRING. The discrepancy is due to AWS-LC's lack
# of support for indefinite OCTET_STRINGS.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great points and indeed there seems to be something we overlooked here. I'll sync up internally with our team to figure how to properly adjust this. Thanks!!

@samuel40791765 samuel40791765 force-pushed the aws-lc-support-2 branch 5 times, most recently from 1fdf97d to 637b6c6 Compare February 18, 2025 03:21
AWS-LC does not support the KEY_SIG or KEY_EX flags that were only ever
supported by old MSIE.
AWS-LC does not support BN_FLG_CONSTTIME due to its historically
inconsistent constant-time guarantees.
AWS-LC has a few minor functionalities removed from NCONF_get_string.

1. Expanding of $foo to a previously-parsed value was removed.
2. OpenSSL falls back to using "default" with an unknown "section".
   AWS-LC does not support this behavior.
3. AWS-LC does not support parsing environment variables with "ENV"
   like LibreSSL.
AWS-LC's ASN1 parsing capabilities led to us examine some of our
ASN1 tests. One test in test_asn1.rb happens to use a tag number
of 8224. There are concerns with larger UNIVERSAL tags being
ambiguous with negative ASN1 Integers, so I've adjusted the test
to use CONTEXT_SPECIFIC instead with the same tag number (8224).
1. AWS-LC has no support for SMIME with PKCS7. That may change in the
   near future, so I've marked that with "pend" for now.
2. AWS-LC doesn't support printing of PKCS7 contents with
   PKCS7_print_ctx.
3. OpenSSL traditionally used indefinite-length encoding with
   ASN1_TFLG_NDEF in its implementation for PKCS7 EncryptedContent.
   AWS-LC uses explicit OCTET STRING headers to encode instead,
   which leads to a slight difference in serialized ASN1 contents
   from the two libraries.
OpenSSL allows invalid EC keys or DH params to be parsed. The consuming
application can then run parameter/key checks to check the validity of
the parameters. We happen to run tests to verify that this behaves as
expected.
AWS-LC on the other hand, directly raises an error and disallows the
invalid state to be parsed, rather than making it parsable and checking
the validity later. Relevant tests have been adjusted accordingly to
reflect this.
EVP_DigestVerify in OpenSSL returns 0 to indicate a signature
verification failure and can return -1  to indicate other
failures, such as invalid ASN1 contents. ruby/openssl also
reflects that by returning false with 0 and raising an error
with -1.
EVP_DigestVerify in AWS-LC simply returns 0 for any failure.
We reecently tweaked some break tests in test_pkey_dh.rb due to
different behavior with OpenSSL in FIPS mode.
AWS-LC does not inherit the same specific behavior, so tests
have been adjusted accordingly.
There are a few SSL discrepencies in AWS-LC when compared to OpenSSL.

1. AWS-LC has slightly different error messages (in all-caps).
2. AWS-LC has no support for DHE ciphersuites.
3. There are no concepts of SSL security levels within AWS-LC.
4. Similar to LibreSSL, there is no support for OPENSSL_CONF.
5. AWS-LC emits slightly different certificate verification logic
   errors compared to current versions of OpenSSL.
The SSL SESSION files we were originally testing against use DHE and
SSLv3. AWS-LC happens to have no support for either and we have newer
possible alternatives available, so I've updated the respective
files to use ECDHE-RSA-AES256-SHA with TLS 1.1 and 1.2. I've verified
that these work as expected with all libcryptos we support.

There are also a few SSL session discrepencies in AWS-LC when
compared to OpenSSL.

1. AWS-LC has no support for internal session caching on the
   client-end.
2. AWS-LC supports internal session caching on the server, but
   SSL_get1_session does not return a resumable session with TLS 1.3
   in AWS-LC. Users have to use the SSL_CTX_sess_set_new_cb
   (ctx.session_new_cb in Ruby) to retrieve the resumable session
   ticket.
3. AWS-LC has no current support for external session caching in TLS
   1.3.
Comment on lines +294 to +300
def assert_sign_verify_false_or_error
begin
assert_equal(false, yield)
rescue => e
assert_kind_of(OpenSSL::PKey::PKeyError, e)
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my earlier suggestion wasn't right as the rescue clause would incorrectly catch an assertion failure from assert_equal (which utilizes exceptions). I think something like this should work:

Suggested change
def assert_sign_verify_false_or_error
begin
assert_equal(false, yield)
rescue => e
assert_kind_of(OpenSSL::PKey::PKeyError, e)
end
end
def assert_sign_verify_false_or_error
ret = yield
rescue => e
assert_kind_of(OpenSSL::PKey::PKeyError, e)
else
assert_equal(false, ret)
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants