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

[3.6] TLS 1.3: call psa_crypto_init #9501

Conversation

gilles-peskine-arm
Copy link
Contributor

@gilles-peskine-arm gilles-peskine-arm commented Aug 23, 2024

An application connecting to a peer that supports both TLS 1.2 and TLS 1.3 did not need to call psa_crypto_init in Mbed TLS 3.5, so it must not be required to call psa_crypto_init in Mbed TLS 3.6. We broke this in Mbed TLS 3.6.0. Fix it. Fixes #9072.

PR checklist

  • changelog provided
  • development PR not required. Since development will be released as Mbed TLS 4.0 which will require having called psa_crypto_init before any TLS connection, there is nothing to do.
  • framework PR not required
  • 3.6 PR here
  • 2.28 PR not required because: bug introduced in 3.6.0 (also TLS 1.3 not in 2.28)
  • tests provided; also run programs/ssl/ssl_client1 against a dual 1.2/1.3 server, which in 3.6.0 failed in with an internal error in psa_generate_key. This can still fail, but later at the end of the handshake, due to How to handle TLS 1.3 new session ticket errors #8749.

@gilles-peskine-arm gilles-peskine-arm added needs-ci Needs to pass CI tests component-tls13 priority-very-high Highest priority - prioritise this over other review work size-xs Estimated task size: extra small (a few hours at most) labels Aug 23, 2024
For backward compatibility with Mbed TLS <=3.5.x, applications must be able
to make a TLS connection with a peer that supports both TLS 1.2 and TLS 1.3,
regardless of whether they call psa_crypto_init(). Since Mbed TLS 3.6.0,
we enable TLS 1.3 in the default configuration, so we must take care of
calling psa_crypto_init() if needed. This is a change from TLS 1.3 in
previous versions, where enabling MBEDTLS_SSL_PROTO_TLS1_3 was a user
choice and could have additional requirement.

This commit removes the compatibility-breaking requirement from the
documentation.

Signed-off-by: Gilles Peskine <[email protected]>
For backward compatibility with Mbed TLS <=3.5.x, applications must be able
to make a TLS connection with a peer that supports both TLS 1.2 and TLS 1.3,
regardless of whether they call psa_crypto_init(). Since Mbed TLS 3.6.0,
we enable TLS 1.3 in the default configuration, so we must take care of
calling psa_crypto_init() if needed. This is a change from TLS 1.3 in
previous versions, where enabling MBEDTLS_SSL_PROTO_TLS1_3 was a user
choice and could have additional requirement.

This commit changes our unit tests to validate that the library
does not have the compatibility-breaking requirement.

Signed-off-by: Gilles Peskine <[email protected]>
… 1.3

For backward compatibility with Mbed TLS <=3.5.x, applications must be able
to make a TLS connection with a peer that supports both TLS 1.2 and TLS 1.3,
regardless of whether they call psa_crypto_init(). Since Mbed TLS 3.6.0,
we enable TLS 1.3 in the default configuration, so we must take care of
calling psa_crypto_init() if needed. This is a change from TLS 1.3 in
previous versions, where enabling MBEDTLS_SSL_PROTO_TLS1_3 was a user
choice and could have additional requirement.

This commit changes our test programs to validate that the library
does not have the compatibility-breaking requirement.

Signed-off-by: Gilles Peskine <[email protected]>
For backward compatibility with Mbed TLS <=3.5.x, applications must be able
to make a TLS connection with a peer that supports both TLS 1.2 and TLS 1.3,
regardless of whether they call psa_crypto_init(). Since Mbed TLS 3.6.0,
we enable TLS 1.3 in the default configuration, so we must take care of
calling psa_crypto_init() if needed. This is a change from TLS 1.3 in
previous versions, where enabling MBEDTLS_SSL_PROTO_TLS1_3 was a user
choice and could have additional requirement.

This commit makes the library call psa_crypto_init() when it needs PSA
crypto in a situation where the application might not have called it,
namely, when starting a TLS 1.3 connection.

Signed-off-by: Gilles Peskine <[email protected]>
mpg
mpg previously approved these changes Aug 26, 2024
Copy link
Contributor

@mpg mpg left a comment

Choose a reason for hiding this comment

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

LGTM if the CI agrees, just suggesting an improvement to some comments.

I wonder a bit about the placement of calls to mbedtls_ssl_tls13_crypto_init(): I believe the ones you picked are correct, but I'm wondering about taking a simpler approach can just putting a single call neat the top of mbedtls_ssl_handshake_step(), guarded by

#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
if (ssl->conf->max_version >= MBEDTLS_SSL_VERSION_TLS1_3)

Wdyt?

It seems that the intention was to only init PSA when we're actually negotiationg TLS 1.3, but on the client when writing the ClientHello extensions we still don't know if the server will pick 1.3, and on the server-side, currently the call is too early in ClientHello parsing for the server to know if this was a 1.2 or 1.3 hello (that check is a few lines below). So, wouldn't it be simpler to just base the call on whether 1.3 is enabled on our side or not (since the call has to occur pretty early in the handshake, before any crypto is done)?

programs/ssl/ssl_client2.c Show resolved Hide resolved
@mpg mpg changed the title TLS 1.3: call psa_crypto_init [3.6] TLS 1.3: call psa_crypto_init Aug 26, 2024
Comment on lines 4929 to 4930
* Otherwise, the handshake may call psa_crypto_init()
* if it ends up negotiating TLS 1.3.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Otherwise, the handshake may call psa_crypto_init() if it ends up negotiating TLS 1.3.

Actually that's not quite accurate: the handshake may call psa_crypto_init if it tries to negotiate TLS 1.3. I left it more open in mbedtls_config.h.

So, wouldn't it be simpler to just base the call on whether 1.3 is enabled on our side or not

Since this is an update to an LTS, I wanted to minimize behavior changes. It's impossible to only add crypto init calls to workflows that do negotiate TLS 1.3, because the negotiation might involve crypto. The most obvious example is that a client needs PSA crypto to send a 1.3 ClientHello, and the server might be 1.2-only. But I wanted to avoid changes that weren't necessary. In particular, a server getting a 1.2-only ClientHello won't start calling psa_crypto_init.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

putting a single call neat the top of mbedtls_ssl_handshake_step()

I wanted to only change 1.3-specific files, so it would be obvious that builds without 1.3 weren't affected. Sure, an #if defined(MBEDTLS_SSL_PROTO_TLS1_3) also makes it locally obvious, but that means yet another ifdef so the code is more complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can we be more precise and say:

  • A client calls psa_crypto_init if it sends a 1.3 hello;
  • A server calls psa_crypto_init if it has 1.3 enabled and receives a 1.3 hello?

I wasn't sure whether we could reliably commit to that. I'm not even 100% sure that this is what's currently happening in all cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we be more precise

I'm actually inclined to be less precise and give ourselves more freedom to change things in the future if needed or just if we want to simplify the code.

I think "may call psa_crypto_init()` if TLS 1.3 is enabled" is good enough. I don't think a user needs to know more than that.

Copy link
Contributor

Choose a reason for hiding this comment

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

putting a single call neat the top of mbedtls_ssl_handshake_step()

I wanted to only change 1.3-specific files, so it would be obvious that builds without 1.3 weren't affected.

Yes, and that's indeed one thing I like about your solution. Mostly I wanted to make the thinking explicit about why they're placed here.

Copy link
Contributor

@mpg mpg Aug 26, 2024

Choose a reason for hiding this comment

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

In particular, a server getting a 1.2-only ClientHello won't start calling psa_crypto_init.

Is that so? When looking at the code, I see PSA being initialized a few lines before we check if the parse ClientHello was 1.2 or 1.3. (Edit: in a build and SSL config where both 1.2 and 1.3 are enabled of course.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, so we do. In an earlier attempt I had the initialization later, but it was too late for when the client sends a key share in ClientHello for PSK or session resumption.

I think it would be nice if a server operating in a 1.2-only environment would not change its behavior, but that would be more complicated to arrange. I think we'd have to have multiple places calling crypto_init in the server code.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be nice if a server operating in a 1.2-only environment would not change its behavior, but that would be more complicated to arrange. I think we'd have to have multiple places calling crypto_init in the server code.

I agree it would be nice, especially in an LTS where we're supposed to minimize changes. I was also afraid that would mean having to call crypto_init in more places, but after taking a look, I think there's an ideal place in ssl_tls13_parse_client_hello(): just when we've determined this is a 1.3 Hello - before that the function only does minimal parsing (memorising pointers and lengths to interesting parts of the message to come back to later when we know the negotiated version).

Wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. I'll try that, with the current code as a fallback.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we be more precise and say:
A client calls psa_crypto_init if it sends a 1.3 hello;
A server calls psa_crypto_init if it has 1.3 enabled and receives a 1.3 hello?

In short I'd say, yes let's definitely try to do that, but no let's not announce it in the documentation - I don't think we'll have time to do enough testing to ensure we succeeded, and I'm rather not tie our hands if we want/need to change things in the future.

@gilles-peskine-arm
Copy link
Contributor Author

What do you think about development? We can forward-port this, change nothing, or make a different patch. We don't need this at all in 4.0, which is an argument to do nothing. On the other hand, tests/include/test/psa_crypto_helpers.h is going to be shared between 3.6 and development, and the patch I made there must not be done without the rest. On the gripping hand, that file has a lot of complexity around when to initialize PSA that we won't want in 4.x anyway, so we might as well start diverging.

* directly performed by TLS 1.3 code. As a consequence, you must
* call psa_crypto_init() before the first TLS 1.3 handshake.
* directly performed by TLS 1.3 code. As a consequence, when TLS 1.3
* is enabled, a TLS handshake may call psa_crypto_init(), even
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we have a changelog entry announcing that psa_crypto_init will now sometimes be called when starting a TLS connection (even a 1.2 connection)?

Copy link
Contributor

@mpg mpg Aug 26, 2024

Choose a reason for hiding this comment

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

Depends, what's observable from a user perspective about psa_crypto_init() being called?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what's observable from a user perspective about psa_crypto_init() being called?

  • It can fail. Unlikely in an application that's doing TLS, but possible.
    • Entropy failure (but how are you getting entropy for the RNG passed to TLS).
    • Stray file that screws up PSA storage initialization (but how would that stray file have been created).
    • Low resources (but you don't really stand a chance of doing a TLS connection then).
  • It may leave resources around at the end, so a memory leak detector would complain. (That's why we're calling mbedtls_psa_crypto_free unconditionally in the tests.)

Those are pretty exotic concerns in an application that's doing a TLS connection, which is why I'm not sure.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then I think as a precaution we should add a brief ChangeLog entry about it.

@ronald-cron-arm ronald-cron-arm self-requested a review August 26, 2024 09:14
@gilles-peskine-arm gilles-peskine-arm added needs-review Every commit must be reviewed by at least two team members, and removed needs-ci Needs to pass CI tests labels Aug 26, 2024
@mpg
Copy link
Contributor

mpg commented Aug 26, 2024

What do you think about development?

Let's get this merged in 3.6 first (hopefully today) and have a discussion about development later (say, tomorrow).

Signed-off-by: Gilles Peskine <[email protected]>
This reduces the workflows where psa_crypto_init is called when not
necessary: it won't be called when a dual-version server receives a 1.2-only
ClientHello.

Signed-off-by: Gilles Peskine <[email protected]>
Copy link
Contributor

@mpg mpg left a comment

Choose a reason for hiding this comment

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

LGTM, thanks! Let's hope the CI agrees.

@gilles-peskine-arm gilles-peskine-arm removed the needs-ci Needs to pass CI tests label Aug 26, 2024
Copy link
Contributor

@ronald-cron-arm ronald-cron-arm left a comment

Choose a reason for hiding this comment

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

Only one suggestion, otherwise this looks good to me.

@@ -1141,6 +1141,11 @@ int mbedtls_ssl_tls13_write_client_hello_exts(mbedtls_ssl_context *ssl,

*out_len = 0;

ret = mbedtls_ssl_tls13_crypto_init(ssl);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this would more naturally fit in ssl_prepare_client_hello() in ssl_client.c where we know for the first time if we are going to propose TLS 1.3 to the server or not: after lines 771 to 783 if ssl->tls_version == MBEDTLS_SSL_VERSION_TLS1_3.

After the TLS 1.2 specific code (from line 815 to 844), instead of:

#if defined(MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE)                              
    if (ssl->tls_version == MBEDTLS_SSL_VERSION_TLS1_3) {                       
        /*                                                                      
         * Create a legacy session identifier for the purpose of middlebox      
         * compatibility only if one has not been created already, which is     
         * the case if we are here for the TLS 1.3 second ClientHello.          
         *                                                                      
         * Versions of TLS before TLS 1.3 supported a "session resumption"      
         * feature which has been merged with pre-shared keys in TLS 1.3        
         * version. A client which has a cached session ID set by a pre-TLS 1.3 
         * server SHOULD set this field to that value. In compatibility mode,   
         * this field MUST be non-empty, so a client not offering a pre-TLS 1.3 
         * session MUST generate a new 32-byte value. This value need not be    
         * random but SHOULD be unpredictable to avoid implementations fixating 
         * on a specific value (also known as ossification). Otherwise, it MUST 
         * be set as a zero-length vector ( i.e., a zero-valued single byte     
         * length field ).                                                      
         */                                                                     
        session_id_len = 32;                                                    
    }                                                                           
#endif /* MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE */

we could have something like (not tested):

#if defined(MBEDTLS_SSL_PROTO_TLS1_3)                              
    if (ssl->tls_version == MBEDTLS_SSL_VERSION_TLS1_3) { 
        ret = mbedtls_ssl_tls13_crypto_init(ssl);
        if (ret != 0) {
            return ret;
        }
            
#if defined(MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE)
        /*                                                                      
         * Create a legacy session identifier for the purpose of middlebox      
         * compatibility only if one has not been created already, which is     
         * the case if we are here for the TLS 1.3 second ClientHello.          
         *                                                                      
         * Versions of TLS before TLS 1.3 supported a "session resumption"      
         * feature which has been merged with pre-shared keys in TLS 1.3        
         * version. A client which has a cached session ID set by a pre-TLS 1.3 
         * server SHOULD set this field to that value. In compatibility mode,   
         * this field MUST be non-empty, so a client not offering a pre-TLS 1.3 
         * session MUST generate a new 32-byte value. This value need not be    
         * random but SHOULD be unpredictable to avoid implementations fixating 
         * on a specific value (also known as ossification). Otherwise, it MUST 
         * be set as a zero-length vector ( i.e., a zero-valued single byte     
         * length field ).                                                      
         */                                                                     
        session_id_len = 32;
#endif                                                    
    }                                                                           
#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */

Wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So a little earlier, that should work. But it means changing a non-13 file, which I'd prefer to avoid. Also it's buried right in the middle of a function, so a failure there (admittedly unlikely) could leave the context in a worse state. On balance I prefer the place where it is now. And I'd rather avoid one more round of rework given the urgency.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, fair enough, let's go with what we have.

Copy link
Contributor

@ronald-cron-arm ronald-cron-arm left a comment

Choose a reason for hiding this comment

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

LGTM, thanks.

@gilles-peskine-arm gilles-peskine-arm removed the needs-review Every commit must be reviewed by at least two team members, label Aug 26, 2024
@gilles-peskine-arm gilles-peskine-arm added this pull request to the merge queue Aug 26, 2024
Merged via the queue into Mbed-TLS:mbedtls-3.6 with commit 7defa41 Aug 26, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component-tls13 priority-very-high Highest priority - prioritise this over other review work size-xs Estimated task size: extra small (a few hours at most)
Projects
Status: 3.6.1 patch release
Development

Successfully merging this pull request may close these issues.

3 participants