RFC 8448 contains multiple TLS 1.3 message traces, this commit adds two
new test cases focusing on key derivation:
- Simple 1-RTT Handshake
- Resumed 0-RTT Handshake
Additionally, the whole test suite is restructured and duplicate code is
removed and consolidated.
This way the client also properly considers the TLS version and the signature
schemes supported by the server.
Co-authored-by: Tobias Brunner <tobias@strongswan.org>
This commit also addresses the side effect that additional messages have
an influence on the derivation of the application traffic secrets. Therefore,
key derivation is relocated after the server finished message has been sent,
so the additional messages from the client (Certificate, CertificateVerify)
don't affect the key derivation. Only the outbound key is switched there, the
inbound key remains in use until the client's finished message has been
processed.
This change mainly affects legacy TLS versions because TLS 1.3
connections are terminated by the server once the peer does not send a
CertificateVerify message next to its empty Certificate message.
The cached traffic secrets change once the application traffic secrets
are derived, but we must always use the correct base key to derive the
finished message, which are the handshake traffic secrets (RFC 8446,
section 4.4).
This won't work if the client doesn't send a `signature_algorithms`
extension. But since the default is SHA1/RSA, most will send it to at
least announce stronger hash algorithms if not ECDSA.
strongSwan supports RSA_PSS_RSAE schemes for signing but does not
differentiate between rsaEncryption and rsassaPss encoding. Thus
RSA_PSS_PSS schemes are only used for verifying signatures.
A client can send one or multiple key shares from which the server picks
one it supports (checked in its preferred order). A retry is requested if
none of the key shares are supported.
Adds support to request and handle retries with a different DH group.
Only the first key share extension sent by the client is currently
considered, so this might result in protocol errors if the server requests
a group for which the client already sent a key share.
List building also added an additional length field which is required by
client-side TLS extensions but not for server-side certificate request
extension. Now the method only returns a list of supported signature
algorithms and the implementation is responsible to add additional
length fields.
Fixes: 07f826af67 ("Fixed encoding of TLS extensions (elliptic_curves and signature_algorithms)")
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
- Encrypt then MAC
- Extended master secret
- Session ticket
and also add missing suites in the unit test.
If we don't do this, we might negotiate a TLS version for which we don't
have any suites configured, so that the cipher suite negotiation
subsequently fails.
Only suggest TLS versions of supported cipher suites. For instance, do not
suggest TLS 1.3 if none of its cipher suites (requiring GCM/CCM or
ChaPoly) are available.
Even though we return from build(), we are not actually sending a response,
so we can't return NEED_MORE (would send an invalid ClientHello message) and
if we return SUCCESS, the EAP layer treats this as failure (there is a comment
in eap_authenticator_t about client methods never returning SUCCESS from
process()). Instead we return INVALID_STATE, which allows tls_t.build() to
exit from the build() loop immediately and send the already generated Finished
message.
We generate material for both MSK and EMSK even though we only need the
former. Because HKDF-Expand-Label(), on which the export functionality
is based, encodes the requested key length, we have to allocate the same
number of bytes as e.g. FreeRADIUS does (i.e. if we only request 64
bytes, those won't be the same as the first 64 bytes after requesting
128 bytes).
Unfortunately, key derivation for TLS-based methods is currently not
standardized for TLS 1.3. There is a draft [1], which defines a scheme
that's different from previous versions (instead of individual label
strings it uses a single one and passes the EAP type/code as context
value to TLS-Export()). The current code is compatible to FreeRADIUS
3.0.x, which doesn't implement it according to that draft yet (there are
unreleased changes for EAP-TLS, not for the other methods, but these only
switch the label, no context value is passed). In a separate draft
for EAP-TLS [2] there is an altogether different scheme defined in the
latest version (label combined with EAP method, no context and separate
derivation for MSK and EMSK).
So this is a mess and we will have to change this later with the inevitable
compatibility issues (we should definitely disable TLS 1.3 by default).
[1] https://tools.ietf.org/html/draft-ietf-emu-tls-eap-types
[2] https://tools.ietf.org/html/draft-ietf-emu-eap-tls13
PKCS#1 v1.5 signatures are not defined for use with TLS 1.3 (they can
only appear in certificates, we now send a signature_algorithms_cert
extension to indicate support for them). So for RSA certificates, we
must support RSA-PSS signatures.
There are two sets of schemes, that are differentiated by the type of
RSA key used for the signature, one is for classic RSA keys (rsaEncryption
OID), which can also be used with PKCS#1 when using TLS 1.2, the other
is for RSA-PSS keys (RSASSA-PSS OID), which are not yet commonly
used (and can't be generated by our pki tool). According to the RFC,
PSS must also be supported for TLS 1.2 if the schemes are included in
the signature_algorithms extension (e.g. OpenSSL does not use PKCS#1 v1.5
anymore if PSS is proposed).
This changes how these schemes are stored and enumerated (they are not
treated as combination of hash algo and key type anymore).
Legacy schemes (MD5/SHA-1) are removed.
With the previous code, there was an issue when replying to TLS 1.3
post-handshake messages. In this case, SUCCESS is eventually returned
from build(), however, no actual data has been received so in_done is 0.
This was interpreted as EOF, plain_eof was set to TRUE and no further data
was read from the socket afterwards.
Returning SUCCESS from build() if the exchange is initiated by
write_(), as is the case with the finished reply, never was a problem
because there the return value of 0 is not interpreted as EOF.