@@ -52,7 +52,63 @@ public void testDTLSv13ServerHelloWithoutKeyShare() throws Exception
5252 alertDescription );
5353 }
5454
55+ /**
56+ * RFC 9147 5.1. DTLS 1.3 has no HelloVerifyRequest, so a server that sent one must not then select DTLS
57+ * 1.3: the handshake would have been reached through a denial-of-service countermeasure DTLS 1.3 does not
58+ * have, and without this check the DTLS 1.2 cookie exchange is a way in to a 1.3 handshake. A
59+ * version-straddling client - one that offers DTLS 1.2 as well - has to accept the HelloVerifyRequest,
60+ * because the peer may genuinely be a DTLS 1.2 server, so the refusal can only come afterwards.
61+ * <p>
62+ * The server half of this sequence is scripted rather than driven by DTLSServerProtocol, because a
63+ * conforming BC server no longer produces it: DTLSServerProtocol.generateServerHello refuses to select
64+ * DTLS 1.3 behind a HelloVerifyRequest front end at all (see
65+ * DTLS13ProtocolTest.testServerRefusesToSelectDTLSv13BehindAHelloVerifyRequest). Only a synthetic peer
66+ * can still exercise the client's side of the rule.
67+ * </p>
68+ */
69+ public void testClientRefusesDTLSv13SelectedAfterAHelloVerifyRequest () throws Exception
70+ {
71+ byte [] cookie = new byte []{ (byte )0xA1 , (byte )0xB2 , (byte )0xC3 , (byte )0xD4 };
72+
73+ byte [][] script = new byte [][]{
74+ // RFC 6347 4.2.1. HelloVerifyRequest, message_seq 0, answering the first ClientHello
75+ createHelloVerifyRequestRecord (cookie , 0 ),
76+ // and then DTLS 1.3 anyway, at the message_seq the client expects a ServerHello at after one
77+ createServerHelloRecord (new byte []{ (byte )0xFE , (byte )0xFC }, 1 , 1 )
78+ };
79+
80+ ScriptedServerHelloTransport transport = new ScriptedServerHelloTransport (script );
81+
82+ TlsFatalAlert fatalAlert = connectAndExpectFatalAlert (transport ,
83+ ProtocolVersion .DTLSv13 .downTo (ProtocolVersion .DTLSv12 ));
84+
85+ assertEquals ("alert for DTLS 1.3 selected after a HelloVerifyRequest" ,
86+ AlertDescription .illegal_parameter , fatalAlert .getAlertDescription ());
87+
88+ /*
89+ * The alert description alone would not say which check raised it - the DTLS 1.3 ServerHello scripted
90+ * here carries no "key_share" either, and that check raises the same alert a few lines later. The
91+ * message, and the fact that the client answered the HelloVerifyRequest with a second ClientHello,
92+ * pin it to the rule under test.
93+ */
94+ assertEquals ("the HelloVerifyRequest check must be the one that raised it" ,
95+ "illegal_parameter(47); Server selected DTLS 1.3 after sending a HelloVerifyRequest" ,
96+ fatalAlert .getMessage ());
97+
98+ assertEquals ("the client must have answered the HelloVerifyRequest" , 2 ,
99+ transport .clientHellosSeen ());
100+ }
101+
55102 private short connectAndExpectFatalAlert (byte [] selectedVersion ) throws Exception
103+ {
104+ ScriptedServerHelloTransport transport = new ScriptedServerHelloTransport (
105+ new byte [][]{ createServerHelloRecord (selectedVersion , 0 , 0 ) });
106+
107+ return connectAndExpectFatalAlert (transport , ProtocolVersion .DTLSv13 .only ()).getAlertDescription ();
108+ }
109+
110+ private TlsFatalAlert connectAndExpectFatalAlert (ScriptedServerHelloTransport transport ,
111+ final ProtocolVersion [] clientVersions ) throws Exception
56112 {
57113 MockDTLSClient client = new MockDTLSClient (null )
58114 {
@@ -63,7 +119,7 @@ public int[] getCipherSuites()
63119
64120 protected ProtocolVersion [] getSupportedVersions ()
65121 {
66- return ProtocolVersion . DTLSv13 . only () ;
122+ return clientVersions ;
67123 }
68124 };
69125
@@ -72,9 +128,6 @@ protected ProtocolVersion[] getSupportedVersions()
72128
73129 DTLSClientProtocol clientProtocol = new DTLSClientProtocol ();
74130
75- ScriptedServerHelloTransport transport = new ScriptedServerHelloTransport (
76- createServerHelloRecord (selectedVersion ));
77-
78131 try
79132 {
80133 clientProtocol .connect (client , transport );
@@ -83,18 +136,35 @@ protected ProtocolVersion[] getSupportedVersions()
83136 {
84137 assertTrue ("server never received a ClientHello" , transport .sawClientHello ());
85138
86- return fatalAlert . getAlertDescription () ;
139+ return fatalAlert ;
87140 }
88141
89142 fail ("expected the client to raise a fatal alert" );
90- return -1 ;
143+ return null ;
144+ }
145+
146+ /**
147+ * RFC 6347 4.2.1. A DTLS plaintext record carrying a HelloVerifyRequest, whose 'server_version' is DTLS
148+ * 1.0 as that section requires regardless of the version being negotiated.
149+ */
150+ private static byte [] createHelloVerifyRequestRecord (byte [] cookie , long recordSeq ) throws IOException
151+ {
152+ ByteArrayOutputStream body = new ByteArrayOutputStream ();
153+ // server_version: DTLS 1.0
154+ body .write (0xFE );
155+ body .write (0xFF );
156+ body .write (cookie .length );
157+ body .write (cookie , 0 , cookie .length );
158+
159+ return createPlaintextRecord (HandshakeType .hello_verify_request , 0 , recordSeq , body .toByteArray ());
91160 }
92161
93162 /**
94163 * A DTLS plaintext record (RFC 9147 4) carrying a single unfragmented ServerHello, selecting the given
95164 * version through the "supported_versions" extension.
96165 */
97- private static byte [] createServerHelloRecord (byte [] selectedVersion ) throws IOException
166+ private static byte [] createServerHelloRecord (byte [] selectedVersion , int messageSeq , long recordSeq )
167+ throws IOException
98168 {
99169 ByteArrayOutputStream extensions = new ByteArrayOutputStream ();
100170 writeUint16 (extensions , ExtensionType .supported_versions );
@@ -120,26 +190,34 @@ private static byte[] createServerHelloRecord(byte[] selectedVersion) throws IOE
120190 writeUint16 (body , extensionsData .length );
121191 body .write (extensionsData , 0 , extensionsData .length );
122192
123- byte [] bodyData = body .toByteArray ();
193+ return createPlaintextRecord (HandshakeType .server_hello , messageSeq , recordSeq , body .toByteArray ());
194+ }
124195
196+ /**
197+ * RFC 9147 4. One epoch-0 plaintext record carrying one unfragmented handshake message. The
198+ * 'sequence_number' is the caller's, because the record layer's replay window discards a repeat of one it
199+ * has already accepted, so each record of a scripted sequence needs its own.
200+ */
201+ private static byte [] createPlaintextRecord (short msgType , int messageSeq , long recordSeq , byte [] bodyData )
202+ throws IOException
203+ {
125204 ByteArrayOutputStream record = new ByteArrayOutputStream ();
126205 record .write (ContentType .handshake );
127206 // legacy_record_version
128207 record .write (0xFE );
129208 record .write (0xFD );
130209 // epoch
131210 writeUint16 (record , 0 );
132- // sequence_number
133- for (int i = 0 ; i < 6 ; ++ i )
211+ // sequence_number (48 bits)
212+ for (int i = 5 ; i >= 0 ; -- i )
134213 {
135- record .write (0 );
214+ record .write (( int )(( recordSeq >>> ( 8 * i )) & 0xFF ) );
136215 }
137216 writeUint16 (record , 12 + bodyData .length );
138217
139- record .write (HandshakeType . server_hello );
218+ record .write (msgType );
140219 writeUint24 (record , bodyData .length );
141- // message_seq
142- writeUint16 (record , 0 );
220+ writeUint16 (record , messageSeq );
143221 // fragment_offset
144222 writeUint24 (record , 0 );
145223 // fragment_length
@@ -163,24 +241,31 @@ private static void writeUint24(ByteArrayOutputStream buf, int i)
163241 }
164242
165243 /**
166- * Answers the first ClientHello with one prepared record and nothing thereafter.
244+ * Answers each ClientHello with the next prepared record, and nothing once the script runs out. One
245+ * record per ClientHello is enough for both shapes used here: a single ServerHello, and a
246+ * HelloVerifyRequest followed by the ServerHello that answers the second ClientHello.
167247 */
168248 private static class ScriptedServerHelloTransport
169249 implements DatagramTransport
170250 {
171- private final byte [] serverHelloRecord ;
251+ private final byte [][] script ;
172252
173- private boolean sawClientHello = false ;
253+ private int clientHellosSeen = 0 ;
174254 private byte [] pending = null ;
175255
176- ScriptedServerHelloTransport (byte [] serverHelloRecord )
256+ ScriptedServerHelloTransport (byte [][] script )
177257 {
178- this .serverHelloRecord = serverHelloRecord ;
258+ this .script = script ;
179259 }
180260
181261 boolean sawClientHello ()
182262 {
183- return sawClientHello ;
263+ return clientHellosSeen > 0 ;
264+ }
265+
266+ int clientHellosSeen ()
267+ {
268+ return clientHellosSeen ;
184269 }
185270
186271 public int getReceiveLimit ()
@@ -196,11 +281,10 @@ public int getSendLimit()
196281 public void send (byte [] buf , int off , int len ) throws IOException
197282 {
198283 // DTLS plaintext record header (13 bytes), then the DTLS handshake message header
199- if (! sawClientHello && len > 13 && (buf [off ] & 0xFF ) == ContentType .handshake
284+ if (clientHellosSeen < script . length && len > 13 && (buf [off ] & 0xFF ) == ContentType .handshake
200285 && (buf [off + 13 ] & 0xFF ) == HandshakeType .client_hello )
201286 {
202- this .sawClientHello = true ;
203- this .pending = serverHelloRecord ;
287+ this .pending = script [clientHellosSeen ++];
204288 }
205289 }
206290
0 commit comments