Skip to content

Commit a1edf1a

Browse files
committed
Assert the HelloRetryRequest synthetic transcript bytes directly, relates to github #1468.
1 parent d9a6c3c commit a1edf1a

1 file changed

Lines changed: 108 additions & 0 deletions

File tree

‎tls/src/test/java/org/bouncycastle/tls/DTLSTranscriptHashTest.java‎

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class DTLSTranscriptHashTest
2727
private static final byte[] CLIENT_HELLO_BODY = body(0x10, 40);
2828
private static final byte[] SERVER_HELLO_BODY = body(0x20, 24);
2929
private static final byte[] CERTIFICATE_BODY = body(0x30, 11);
30+
private static final byte[] HELLO_RETRY_REQUEST_BODY = body(0x50, 18);
3031

3132
/**
3233
* A DTLS 1.3 transcript hashes msg_type || uint24 length || body, and nothing else.
@@ -124,6 +125,100 @@ public void testClientHelloHashedBeforeTheVersionIsKnownUsesTheDTLSv12Form() thr
124125
Arrays.areEqual(sha256(expected.toByteArray()), fixture.transcript()));
125126
}
126127

128+
/**
129+
* RFC 8446 4.4.1. Where a HelloRetryRequest is used the transcript is not ClientHello1 itself but a
130+
* synthetic handshake message standing in for it:
131+
* <pre>
132+
* Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
133+
* Hash(message_hash || (= 254)
134+
* 00 00 Hash.length ||
135+
* Hash(ClientHello1) ||
136+
* HelloRetryRequest || ... || Mn)
137+
* </pre>
138+
* The expected bytes below are assembled from those literal field values, NOT from
139+
* {@link TlsUtils#adjustTranscriptForRetry(TlsHandshakeHash)}: an error in the synthetic message's own
140+
* encoding is invisible to any handshake between two Bouncy Castle peers, which would make the identical
141+
* error on both sides, agree with each other, and fail only against another implementation.
142+
*/
143+
public void testHelloRetryRequestTranscriptUsesTheSyntheticMessageHash() throws Exception
144+
{
145+
Fixture fixture = new Fixture(ProtocolVersion.DTLSv13);
146+
147+
fixture.digest(HandshakeType.client_hello, 0, CLIENT_HELLO_BODY);
148+
fixture.adjustForRetry();
149+
fixture.digest(HandshakeType.server_hello, 1, HELLO_RETRY_REQUEST_BODY);
150+
151+
byte[] transcript = fixture.transcript();
152+
153+
byte[] clientHello1Hash = sha256(tlsHeaderForm(HandshakeType.client_hello, CLIENT_HELLO_BODY));
154+
assertEquals("SHA-256 output length", 32, clientHello1Hash.length);
155+
assertEquals("RFC 8446 4.4.1 message_hash HandshakeType", 254, HandshakeType.message_hash);
156+
157+
ByteArrayOutputStream expected = new ByteArrayOutputStream();
158+
expected.write(254);
159+
expected.write(0x00);
160+
expected.write(0x00);
161+
expected.write(32);
162+
expected.write(clientHello1Hash);
163+
expected.write(tlsHeaderForm(HandshakeType.server_hello, HELLO_RETRY_REQUEST_BODY));
164+
165+
assertTrue("the transcript must be over the RFC 8446 4.4.1 synthetic message_hash",
166+
Arrays.areEqual(sha256(expected.toByteArray()), transcript));
167+
168+
/*
169+
* Negative controls. Each is a plausible way to get the synthetic message wrong that leaves the
170+
* ordering - the only thing the existing inverted-ordering test can catch - intact.
171+
*/
172+
173+
// no substitution at all: ClientHello1 left in the transcript verbatim
174+
ByteArrayOutputStream noSubstitution = new ByteArrayOutputStream();
175+
noSubstitution.write(tlsHeaderForm(HandshakeType.client_hello, CLIENT_HELLO_BODY));
176+
noSubstitution.write(tlsHeaderForm(HandshakeType.server_hello, HELLO_RETRY_REQUEST_BODY));
177+
178+
assertFalse("ClientHello1 must not be left in the transcript",
179+
Arrays.areEqual(sha256(noSubstitution.toByteArray()), transcript));
180+
181+
// the bare digest, with no synthetic message header at all
182+
ByteArrayOutputStream noHeader = new ByteArrayOutputStream();
183+
noHeader.write(clientHello1Hash);
184+
noHeader.write(tlsHeaderForm(HandshakeType.server_hello, HELLO_RETRY_REQUEST_BODY));
185+
186+
assertFalse("the synthetic message_hash header must be present",
187+
Arrays.areEqual(sha256(noHeader.toByteArray()), transcript));
188+
189+
// some other msg_type in the synthetic message
190+
ByteArrayOutputStream wrongType = new ByteArrayOutputStream();
191+
wrongType.write(HandshakeType.client_hello);
192+
wrongType.write(0x00);
193+
wrongType.write(0x00);
194+
wrongType.write(32);
195+
wrongType.write(clientHello1Hash);
196+
wrongType.write(tlsHeaderForm(HandshakeType.server_hello, HELLO_RETRY_REQUEST_BODY));
197+
198+
assertFalse("the synthetic message's msg_type must be message_hash (254)",
199+
Arrays.areEqual(sha256(wrongType.toByteArray()), transcript));
200+
201+
// the 12-byte DTLS header form for the synthetic message, which RFC 9147 5.2 excludes
202+
ByteArrayOutputStream dtlsHeader = new ByteArrayOutputStream();
203+
dtlsHeader.write(dtlsHeaderForm(HandshakeType.message_hash, 0, clientHello1Hash));
204+
dtlsHeader.write(tlsHeaderForm(HandshakeType.server_hello, HELLO_RETRY_REQUEST_BODY));
205+
206+
assertFalse("the synthetic message must use the 4-byte TLS header form",
207+
Arrays.areEqual(sha256(dtlsHeader.toByteArray()), transcript));
208+
209+
// ClientHello1 digested in the 12-byte DTLS form before being substituted
210+
ByteArrayOutputStream dtlsFormClientHello = new ByteArrayOutputStream();
211+
dtlsFormClientHello.write(254);
212+
dtlsFormClientHello.write(0x00);
213+
dtlsFormClientHello.write(0x00);
214+
dtlsFormClientHello.write(32);
215+
dtlsFormClientHello.write(sha256(dtlsHeaderForm(HandshakeType.client_hello, 0, CLIENT_HELLO_BODY)));
216+
dtlsFormClientHello.write(tlsHeaderForm(HandshakeType.server_hello, HELLO_RETRY_REQUEST_BODY));
217+
218+
assertFalse("Hash(ClientHello1) must be over the 4-byte TLS header form",
219+
Arrays.areEqual(sha256(dtlsFormClientHello.toByteArray()), transcript));
220+
}
221+
127222
/**
128223
* HelloVerifyRequest stays out of the transcript in every version.
129224
*/
@@ -202,6 +297,19 @@ void negotiate(ProtocolVersion negotiatedVersion)
202297
context.getSecurityParametersHandshake().negotiatedVersion = negotiatedVersion;
203298
}
204299

300+
/**
301+
* The HelloRetryRequest substitution, driven exactly as DTLSServerProtocol.serverHandshake and
302+
* DTLSClientProtocol.clientHandshake drive it: the PRF is determined, the transcript is replaced, and
303+
* the HelloRetryRequest is then digested on top of the replacement.
304+
*/
305+
void adjustForRetry() throws IOException
306+
{
307+
TlsHandshakeHash handshakeHash = handshake.getHandshakeHash();
308+
handshakeHash.notifyPRFDetermined();
309+
310+
TlsUtils.adjustTranscriptForRetry(handshakeHash);
311+
}
312+
205313
void digest(short msgType, int messageSeq, byte[] msgBody) throws IOException
206314
{
207315
handshake.updateHandshakeMessagesDigest(

0 commit comments

Comments
 (0)