The current TLS standard is not very clear on how versions should be handled. Annex E.1 contains some information, from which we can see that the implicit idea is that the selected version depends on what the client and server support, not on what the client and server may elect to use on a whim. If a client supports up to TLS 1.2 and a server up to TLS 1.1, then they should use TLS 1.1, always. As such, there should be no question of "changing the version" when resuming a session, or renegotiating.
There are several version fields:
- Each "record" has a version field.
- The ClientHello message contains the highest version supported by the client.
- The ServerHello message contains the version that will be used on this connection.
Supposedly, the client will send its first ClientHello as a record bearing a version number 3.x for some value of "x"; 3.0 will maximize interoperability. Once the server has responded with a ServerHello stating that version 3.y will be used, then both client and server must use 3.y records. This makes version jumps somewhat delicate during a renegotiation. Also, CBC-encrypted TLS 1.1+ records are not compatible with SSL 3.0 / TLS 1.0 records (because of the additional per-record IV), so record-level versions are important once the first ChangeCipherSpec has been sent/received.
For a session resumption, annex E.1 states that:
Whenever a client already knows the highest protocol version known to
a server (for example, when resuming a session), it SHOULD initiate
the connection in that native protocol.
from which I infer that if the previous session used version 3.y, then the client should use records with version 3.y. However, nothing prevents it from advertising an higher version number in the ClientHello.
Also, derivation of the master key into encryption and MAC keys uses the TLS PRF, which depends on the protocol version (TLS 1.0 and 1.1 use the same PRF, but not SSL 3.0, and neither TLS 1.2). Resuming a session with a different version is looking for trouble.
The whole area is murky. See for instance this Mozilla/Firefox bug report. In my experience, the following rules should be followed to minimize issues:
Rules for the client:
- Send the first record as version 3.0.
- Always advertise in the ClientHello the maximum supported version (e.g. 3.3 for TLS 1.2).
- When the server responds with a ServerHello, use the version sent by the server as new version for all subsequent records, cryptographic computations, and protocol details.
- Once the server has sent a ServerHello, enforce incoming record versions: subsequent records from the server should use that version. If a record from the server advertises another version, abort.
- When renegotiating, if the server tries to use another version than previously, abort.
Rules for the server:
- Ignore the version on incoming records, during the initial steps of the connection.
- Once you have sent your ServerHello, enforce record versions: all subsequent records from the client should use that version.
- When resuming a session, reuse the same version, as long as the version in the ClientHello allows it; otherwise, do a full handshake (e.g. if the previous session was TLS 1.1 and the new ClientHello says 1.2, resume with version 1.1; if the previous session was TLS 1.1 and the new ClientHello says 1.0, do not resume, do a new full 1.0 handshake).
- When renegotiating, verify that the version advertised in the new ClientHello is compatible with the current version -- but do not change the current version. If the first handshake was 1.1, the second handshake will be 1.1, even if the new ClientHello says 1.2.
It is pretty much guaranteed that there are some widely deployed implementations which get it wrong at some point.