1*e0c4386eSCy Schubert#! /usr/bin/env perl 2*e0c4386eSCy Schubert# Copyright 2016-2021 The OpenSSL Project Authors. All Rights Reserved. 3*e0c4386eSCy Schubert# 4*e0c4386eSCy Schubert# Licensed under the Apache License 2.0 (the "License"). You may not use 5*e0c4386eSCy Schubert# this file except in compliance with the License. You can obtain a copy 6*e0c4386eSCy Schubert# in the file LICENSE in the source distribution or at 7*e0c4386eSCy Schubert# https://www.openssl.org/source/license.html 8*e0c4386eSCy Schubert 9*e0c4386eSCy Schubertuse strict; 10*e0c4386eSCy Schubertuse feature 'state'; 11*e0c4386eSCy Schubert 12*e0c4386eSCy Schubertuse OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/; 13*e0c4386eSCy Schubertuse OpenSSL::Test::Utils; 14*e0c4386eSCy Schubertuse TLSProxy::Proxy; 15*e0c4386eSCy Schubert 16*e0c4386eSCy Schubertmy $test_name = "test_sslrecords"; 17*e0c4386eSCy Schubertsetup($test_name); 18*e0c4386eSCy Schubert 19*e0c4386eSCy Schubertplan skip_all => "TLSProxy isn't usable on $^O" 20*e0c4386eSCy Schubert if $^O =~ /^(VMS)$/; 21*e0c4386eSCy Schubert 22*e0c4386eSCy Schubertplan skip_all => "$test_name needs the dynamic engine feature enabled" 23*e0c4386eSCy Schubert if disabled("engine") || disabled("dynamic-engine"); 24*e0c4386eSCy Schubert 25*e0c4386eSCy Schubertplan skip_all => "$test_name needs the sock feature enabled" 26*e0c4386eSCy Schubert if disabled("sock"); 27*e0c4386eSCy Schubert 28*e0c4386eSCy Schubertplan skip_all => "$test_name needs TLSv1.2 enabled" 29*e0c4386eSCy Schubert if disabled("tls1_2"); 30*e0c4386eSCy Schubert 31*e0c4386eSCy Schubert$ENV{OPENSSL_ia32cap} = '~0x200000200000000'; 32*e0c4386eSCy Schubertmy $proxy = TLSProxy::Proxy->new( 33*e0c4386eSCy Schubert \&add_empty_recs_filter, 34*e0c4386eSCy Schubert cmdstr(app(["openssl"]), display => 1), 35*e0c4386eSCy Schubert srctop_file("apps", "server.pem"), 36*e0c4386eSCy Schubert (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE}) 37*e0c4386eSCy Schubert); 38*e0c4386eSCy Schubert 39*e0c4386eSCy Schubertmy $boundary_test_type; 40*e0c4386eSCy Schubertmy $fatal_alert = 0; # set by filters at expected fatal alerts 41*e0c4386eSCy Schubert 42*e0c4386eSCy Schubert#Test 1: Injecting out of context empty records should fail 43*e0c4386eSCy Schubertmy $content_type = TLSProxy::Record::RT_APPLICATION_DATA; 44*e0c4386eSCy Schubertmy $inject_recs_num = 1; 45*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 46*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3"); 47*e0c4386eSCy Schubert$proxy->start() or plan skip_all => "Unable to start up Proxy for tests"; 48*e0c4386eSCy Schubertplan tests => 20; 49*e0c4386eSCy Schubertok($fatal_alert, "Out of context empty records test"); 50*e0c4386eSCy Schubert 51*e0c4386eSCy Schubert#Test 2: Injecting in context empty records should succeed 52*e0c4386eSCy Schubert$proxy->clear(); 53*e0c4386eSCy Schubert$content_type = TLSProxy::Record::RT_HANDSHAKE; 54*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 55*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3"); 56*e0c4386eSCy Schubert$proxy->start(); 57*e0c4386eSCy Schubertok(TLSProxy::Message->success(), "In context empty records test"); 58*e0c4386eSCy Schubert 59*e0c4386eSCy Schubert#Test 3: Injecting too many in context empty records should fail 60*e0c4386eSCy Schubert$fatal_alert = 0; 61*e0c4386eSCy Schubert$proxy->clear(); 62*e0c4386eSCy Schubert#We allow 32 consecutive in context empty records 63*e0c4386eSCy Schubert$inject_recs_num = 33; 64*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 65*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3"); 66*e0c4386eSCy Schubert$proxy->start(); 67*e0c4386eSCy Schubertok($fatal_alert, "Too many in context empty records test"); 68*e0c4386eSCy Schubert 69*e0c4386eSCy Schubert#Test 4: Injecting a fragmented fatal alert should fail. We expect the server to 70*e0c4386eSCy Schubert# send back an alert of its own because it cannot handle fragmented 71*e0c4386eSCy Schubert# alerts 72*e0c4386eSCy Schubert$fatal_alert = 0; 73*e0c4386eSCy Schubert$proxy->clear(); 74*e0c4386eSCy Schubert$proxy->filter(\&add_frag_alert_filter); 75*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 76*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3"); 77*e0c4386eSCy Schubert$proxy->start(); 78*e0c4386eSCy Schubertok($fatal_alert, "Fragmented alert records test"); 79*e0c4386eSCy Schubert 80*e0c4386eSCy Schubert#Run some SSLv2 ClientHello tests 81*e0c4386eSCy Schubert 82*e0c4386eSCy Schubertuse constant { 83*e0c4386eSCy Schubert TLSV1_2_IN_SSLV2 => 0, 84*e0c4386eSCy Schubert SSLV2_IN_SSLV2 => 1, 85*e0c4386eSCy Schubert FRAGMENTED_IN_TLSV1_2 => 2, 86*e0c4386eSCy Schubert FRAGMENTED_IN_SSLV2 => 3, 87*e0c4386eSCy Schubert ALERT_BEFORE_SSLV2 => 4 88*e0c4386eSCy Schubert}; 89*e0c4386eSCy Schubert 90*e0c4386eSCy Schubert# The TLSv1.2 in SSLv2 ClientHello need to run at security level 0 91*e0c4386eSCy Schubert# because in a SSLv2 ClientHello we can't send extentions to indicate 92*e0c4386eSCy Schubert# which signature algorithm we want to use, and the default is SHA1. 93*e0c4386eSCy Schubert 94*e0c4386eSCy Schubert#Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello 95*e0c4386eSCy Schubertmy $sslv2testtype = TLSV1_2_IN_SSLV2; 96*e0c4386eSCy Schubert$proxy->clear(); 97*e0c4386eSCy Schubert$proxy->filter(\&add_sslv2_filter); 98*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 99*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3 -legacy_renegotiation"); 100*e0c4386eSCy Schubert$proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 101*e0c4386eSCy Schubert$proxy->start(); 102*e0c4386eSCy Schubertok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test"); 103*e0c4386eSCy Schubert 104*e0c4386eSCy Schubert#Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't 105*e0c4386eSCy Schubert# support this so it should fail. We actually treat it as an unknown 106*e0c4386eSCy Schubert# protocol so we don't even send an alert in this case. 107*e0c4386eSCy Schubert$sslv2testtype = SSLV2_IN_SSLV2; 108*e0c4386eSCy Schubert$proxy->clear(); 109*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 110*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3"); 111*e0c4386eSCy Schubert$proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 112*e0c4386eSCy Schubert$proxy->start(); 113*e0c4386eSCy Schubertok(TLSProxy::Message->fail(), "SSLv2 in SSLv2 ClientHello test"); 114*e0c4386eSCy Schubert 115*e0c4386eSCy Schubert#Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test 116*e0c4386eSCy Schubert# at all, but it gives us confidence that Test 8 fails for the right 117*e0c4386eSCy Schubert# reasons 118*e0c4386eSCy Schubert$sslv2testtype = FRAGMENTED_IN_TLSV1_2; 119*e0c4386eSCy Schubert$proxy->clear(); 120*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 121*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3"); 122*e0c4386eSCy Schubert$proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 123*e0c4386eSCy Schubert$proxy->start(); 124*e0c4386eSCy Schubertok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test"); 125*e0c4386eSCy Schubert 126*e0c4386eSCy Schubert#Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2 127*e0c4386eSCy Schubert# record; and another TLS1.2 record. This isn't allowed so should fail 128*e0c4386eSCy Schubert$sslv2testtype = FRAGMENTED_IN_SSLV2; 129*e0c4386eSCy Schubert$proxy->clear(); 130*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 131*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3"); 132*e0c4386eSCy Schubert$proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 133*e0c4386eSCy Schubert$proxy->start(); 134*e0c4386eSCy Schubertok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test"); 135*e0c4386eSCy Schubert 136*e0c4386eSCy Schubert#Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should 137*e0c4386eSCy Schubert# fail because an SSLv2 ClientHello must be the first record. 138*e0c4386eSCy Schubert$sslv2testtype = ALERT_BEFORE_SSLV2; 139*e0c4386eSCy Schubert$proxy->clear(); 140*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 141*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3"); 142*e0c4386eSCy Schubert$proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 143*e0c4386eSCy Schubert$proxy->start(); 144*e0c4386eSCy Schubertok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test"); 145*e0c4386eSCy Schubert 146*e0c4386eSCy Schubert#Unrecognised record type tests 147*e0c4386eSCy Schubert 148*e0c4386eSCy Schubert#Test 10: Sending an unrecognised record type in TLS1.2 should fail 149*e0c4386eSCy Schubert$fatal_alert = 0; 150*e0c4386eSCy Schubert$proxy->clear(); 151*e0c4386eSCy Schubert$proxy->serverflags("-tls1_2"); 152*e0c4386eSCy Schubert$proxy->clientflags("-no_tls1_3"); 153*e0c4386eSCy Schubert$proxy->filter(\&add_unknown_record_type); 154*e0c4386eSCy Schubert$proxy->start(); 155*e0c4386eSCy Schubertok($fatal_alert, "Unrecognised record type in TLS1.2"); 156*e0c4386eSCy Schubert 157*e0c4386eSCy SchubertSKIP: { 158*e0c4386eSCy Schubert skip "TLSv1.1 disabled", 1 if disabled("tls1_1"); 159*e0c4386eSCy Schubert 160*e0c4386eSCy Schubert #Test 11: Sending an unrecognised record type in TLS1.1 should fail 161*e0c4386eSCy Schubert $fatal_alert = 0; 162*e0c4386eSCy Schubert $proxy->clear(); 163*e0c4386eSCy Schubert $proxy->clientflags("-tls1_1 -cipher DEFAULT:\@SECLEVEL=0"); 164*e0c4386eSCy Schubert $proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 165*e0c4386eSCy Schubert $proxy->start(); 166*e0c4386eSCy Schubert ok($fatal_alert, "Unrecognised record type in TLS1.1"); 167*e0c4386eSCy Schubert} 168*e0c4386eSCy Schubert 169*e0c4386eSCy Schubert#Test 12: Sending a different record version in TLS1.2 should fail 170*e0c4386eSCy Schubert$fatal_alert = 0; 171*e0c4386eSCy Schubert$proxy->clear(); 172*e0c4386eSCy Schubert$proxy->clientflags("-tls1_2"); 173*e0c4386eSCy Schubert$proxy->filter(\&change_version); 174*e0c4386eSCy Schubert$proxy->start(); 175*e0c4386eSCy Schubertok($fatal_alert, "Changed record version in TLS1.2"); 176*e0c4386eSCy Schubert 177*e0c4386eSCy Schubert#TLS1.3 specific tests 178*e0c4386eSCy SchubertSKIP: { 179*e0c4386eSCy Schubert skip "TLSv1.3 disabled", 8 180*e0c4386eSCy Schubert if disabled("tls1_3") || (disabled("ec") && disabled("dh")); 181*e0c4386eSCy Schubert 182*e0c4386eSCy Schubert #Test 13: Sending a different record version in TLS1.3 should fail 183*e0c4386eSCy Schubert $proxy->clear(); 184*e0c4386eSCy Schubert $proxy->filter(\&change_version); 185*e0c4386eSCy Schubert $proxy->start(); 186*e0c4386eSCy Schubert ok(TLSProxy::Message->fail(), "Changed record version in TLS1.3"); 187*e0c4386eSCy Schubert 188*e0c4386eSCy Schubert #Test 14: Sending an unrecognised record type in TLS1.3 should fail 189*e0c4386eSCy Schubert $fatal_alert = 0; 190*e0c4386eSCy Schubert $proxy->clear(); 191*e0c4386eSCy Schubert $proxy->filter(\&add_unknown_record_type); 192*e0c4386eSCy Schubert $proxy->start(); 193*e0c4386eSCy Schubert ok($fatal_alert, "Unrecognised record type in TLS1.3"); 194*e0c4386eSCy Schubert 195*e0c4386eSCy Schubert #Test 15: Sending an outer record type other than app data once encrypted 196*e0c4386eSCy Schubert #should fail 197*e0c4386eSCy Schubert $fatal_alert = 0; 198*e0c4386eSCy Schubert $proxy->clear(); 199*e0c4386eSCy Schubert $proxy->filter(\&change_outer_record_type); 200*e0c4386eSCy Schubert $proxy->start(); 201*e0c4386eSCy Schubert ok($fatal_alert, "Wrong outer record type in TLS1.3"); 202*e0c4386eSCy Schubert 203*e0c4386eSCy Schubert use constant { 204*e0c4386eSCy Schubert DATA_AFTER_SERVER_HELLO => 0, 205*e0c4386eSCy Schubert DATA_AFTER_FINISHED => 1, 206*e0c4386eSCy Schubert DATA_AFTER_KEY_UPDATE => 2, 207*e0c4386eSCy Schubert DATA_BETWEEN_KEY_UPDATE => 3, 208*e0c4386eSCy Schubert NO_DATA_BETWEEN_KEY_UPDATE => 4, 209*e0c4386eSCy Schubert }; 210*e0c4386eSCy Schubert 211*e0c4386eSCy Schubert #Test 16: Sending a ServerHello which doesn't end on a record boundary 212*e0c4386eSCy Schubert # should fail 213*e0c4386eSCy Schubert $fatal_alert = 0; 214*e0c4386eSCy Schubert $proxy->clear(); 215*e0c4386eSCy Schubert $boundary_test_type = DATA_AFTER_SERVER_HELLO; 216*e0c4386eSCy Schubert $proxy->filter(\¬_on_record_boundary); 217*e0c4386eSCy Schubert $proxy->start(); 218*e0c4386eSCy Schubert ok($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)"); 219*e0c4386eSCy Schubert 220*e0c4386eSCy Schubert #Test 17: Sending a Finished which doesn't end on a record boundary 221*e0c4386eSCy Schubert # should fail 222*e0c4386eSCy Schubert $fatal_alert = 0; 223*e0c4386eSCy Schubert $proxy->clear(); 224*e0c4386eSCy Schubert $boundary_test_type = DATA_AFTER_FINISHED; 225*e0c4386eSCy Schubert $proxy->start(); 226*e0c4386eSCy Schubert ok($fatal_alert, "Record not on boundary in TLS1.3 (Finished)"); 227*e0c4386eSCy Schubert 228*e0c4386eSCy Schubert #Test 18: Sending a KeyUpdate which doesn't end on a record boundary 229*e0c4386eSCy Schubert # should fail 230*e0c4386eSCy Schubert $fatal_alert = 0; 231*e0c4386eSCy Schubert $proxy->clear(); 232*e0c4386eSCy Schubert $boundary_test_type = DATA_AFTER_KEY_UPDATE; 233*e0c4386eSCy Schubert $proxy->start(); 234*e0c4386eSCy Schubert ok($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)"); 235*e0c4386eSCy Schubert 236*e0c4386eSCy Schubert #Test 19: Sending application data in the middle of a fragmented KeyUpdate 237*e0c4386eSCy Schubert # should fail. Strictly speaking this is not a record boundary test 238*e0c4386eSCy Schubert # but we use the same filter. 239*e0c4386eSCy Schubert $fatal_alert = 0; 240*e0c4386eSCy Schubert $proxy->clear(); 241*e0c4386eSCy Schubert $boundary_test_type = DATA_BETWEEN_KEY_UPDATE; 242*e0c4386eSCy Schubert $proxy->start(); 243*e0c4386eSCy Schubert ok($fatal_alert, "Data between KeyUpdate"); 244*e0c4386eSCy Schubert 245*e0c4386eSCy Schubert #Test 20: Fragmented KeyUpdate. This should succeed. Strictly speaking this 246*e0c4386eSCy Schubert # is not a record boundary test but we use the same filter. 247*e0c4386eSCy Schubert $proxy->clear(); 248*e0c4386eSCy Schubert $boundary_test_type = NO_DATA_BETWEEN_KEY_UPDATE; 249*e0c4386eSCy Schubert $proxy->start(); 250*e0c4386eSCy Schubert ok(TLSProxy::Message->success(), "No data between KeyUpdate"); 251*e0c4386eSCy Schubert } 252*e0c4386eSCy Schubert 253*e0c4386eSCy Schubert 254*e0c4386eSCy Schubertsub add_empty_recs_filter 255*e0c4386eSCy Schubert{ 256*e0c4386eSCy Schubert my $proxy = shift; 257*e0c4386eSCy Schubert my $records = $proxy->record_list; 258*e0c4386eSCy Schubert 259*e0c4386eSCy Schubert # We're only interested in the initial ClientHello 260*e0c4386eSCy Schubert if ($proxy->flight != 0) { 261*e0c4386eSCy Schubert $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10; 262*e0c4386eSCy Schubert return; 263*e0c4386eSCy Schubert } 264*e0c4386eSCy Schubert 265*e0c4386eSCy Schubert for (my $i = 0; $i < $inject_recs_num; $i++) { 266*e0c4386eSCy Schubert my $record = TLSProxy::Record->new( 267*e0c4386eSCy Schubert 0, 268*e0c4386eSCy Schubert $content_type, 269*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 270*e0c4386eSCy Schubert 0, 271*e0c4386eSCy Schubert 0, 272*e0c4386eSCy Schubert 0, 273*e0c4386eSCy Schubert 0, 274*e0c4386eSCy Schubert "", 275*e0c4386eSCy Schubert "" 276*e0c4386eSCy Schubert ); 277*e0c4386eSCy Schubert push @{$records}, $record; 278*e0c4386eSCy Schubert } 279*e0c4386eSCy Schubert} 280*e0c4386eSCy Schubert 281*e0c4386eSCy Schubertsub add_frag_alert_filter 282*e0c4386eSCy Schubert{ 283*e0c4386eSCy Schubert my $proxy = shift; 284*e0c4386eSCy Schubert my $records = $proxy->record_list; 285*e0c4386eSCy Schubert my $byte; 286*e0c4386eSCy Schubert 287*e0c4386eSCy Schubert # We're only interested in the initial ClientHello 288*e0c4386eSCy Schubert if ($proxy->flight != 0) { 289*e0c4386eSCy Schubert $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10; 290*e0c4386eSCy Schubert return; 291*e0c4386eSCy Schubert } 292*e0c4386eSCy Schubert 293*e0c4386eSCy Schubert # Add a zero length fragment first 294*e0c4386eSCy Schubert #my $record = TLSProxy::Record->new( 295*e0c4386eSCy Schubert # 0, 296*e0c4386eSCy Schubert # TLSProxy::Record::RT_ALERT, 297*e0c4386eSCy Schubert # TLSProxy::Record::VERS_TLS_1_2, 298*e0c4386eSCy Schubert # 0, 299*e0c4386eSCy Schubert # 0, 300*e0c4386eSCy Schubert # 0, 301*e0c4386eSCy Schubert # "", 302*e0c4386eSCy Schubert # "" 303*e0c4386eSCy Schubert #); 304*e0c4386eSCy Schubert #push @{$proxy->record_list}, $record; 305*e0c4386eSCy Schubert 306*e0c4386eSCy Schubert # Now add the alert level (Fatal) as a separate record 307*e0c4386eSCy Schubert $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL); 308*e0c4386eSCy Schubert my $record = TLSProxy::Record->new( 309*e0c4386eSCy Schubert 0, 310*e0c4386eSCy Schubert TLSProxy::Record::RT_ALERT, 311*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 312*e0c4386eSCy Schubert 1, 313*e0c4386eSCy Schubert 0, 314*e0c4386eSCy Schubert 1, 315*e0c4386eSCy Schubert 1, 316*e0c4386eSCy Schubert $byte, 317*e0c4386eSCy Schubert $byte 318*e0c4386eSCy Schubert ); 319*e0c4386eSCy Schubert push @{$records}, $record; 320*e0c4386eSCy Schubert 321*e0c4386eSCy Schubert # And finally the description (Unexpected message) in a third record 322*e0c4386eSCy Schubert $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE); 323*e0c4386eSCy Schubert $record = TLSProxy::Record->new( 324*e0c4386eSCy Schubert 0, 325*e0c4386eSCy Schubert TLSProxy::Record::RT_ALERT, 326*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 327*e0c4386eSCy Schubert 1, 328*e0c4386eSCy Schubert 0, 329*e0c4386eSCy Schubert 1, 330*e0c4386eSCy Schubert 1, 331*e0c4386eSCy Schubert $byte, 332*e0c4386eSCy Schubert $byte 333*e0c4386eSCy Schubert ); 334*e0c4386eSCy Schubert push @{$records}, $record; 335*e0c4386eSCy Schubert} 336*e0c4386eSCy Schubert 337*e0c4386eSCy Schubertsub add_sslv2_filter 338*e0c4386eSCy Schubert{ 339*e0c4386eSCy Schubert my $proxy = shift; 340*e0c4386eSCy Schubert my $clienthello; 341*e0c4386eSCy Schubert my $record; 342*e0c4386eSCy Schubert 343*e0c4386eSCy Schubert # We're only interested in the initial ClientHello 344*e0c4386eSCy Schubert if ($proxy->flight != 0) { 345*e0c4386eSCy Schubert return; 346*e0c4386eSCy Schubert } 347*e0c4386eSCy Schubert 348*e0c4386eSCy Schubert # Ditch the real ClientHello - we're going to replace it with our own 349*e0c4386eSCy Schubert shift @{$proxy->record_list}; 350*e0c4386eSCy Schubert 351*e0c4386eSCy Schubert if ($sslv2testtype == ALERT_BEFORE_SSLV2) { 352*e0c4386eSCy Schubert my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL, 353*e0c4386eSCy Schubert TLSProxy::Message::AL_DESC_NO_RENEGOTIATION); 354*e0c4386eSCy Schubert my $alertlen = length $alert; 355*e0c4386eSCy Schubert $record = TLSProxy::Record->new( 356*e0c4386eSCy Schubert 0, 357*e0c4386eSCy Schubert TLSProxy::Record::RT_ALERT, 358*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 359*e0c4386eSCy Schubert $alertlen, 360*e0c4386eSCy Schubert 0, 361*e0c4386eSCy Schubert $alertlen, 362*e0c4386eSCy Schubert $alertlen, 363*e0c4386eSCy Schubert $alert, 364*e0c4386eSCy Schubert $alert 365*e0c4386eSCy Schubert ); 366*e0c4386eSCy Schubert 367*e0c4386eSCy Schubert push @{$proxy->record_list}, $record; 368*e0c4386eSCy Schubert } 369*e0c4386eSCy Schubert 370*e0c4386eSCy Schubert if ($sslv2testtype == ALERT_BEFORE_SSLV2 371*e0c4386eSCy Schubert || $sslv2testtype == TLSV1_2_IN_SSLV2 372*e0c4386eSCy Schubert || $sslv2testtype == SSLV2_IN_SSLV2) { 373*e0c4386eSCy Schubert # This is an SSLv2 format ClientHello 374*e0c4386eSCy Schubert $clienthello = 375*e0c4386eSCy Schubert pack "C44", 376*e0c4386eSCy Schubert 0x01, # ClientHello 377*e0c4386eSCy Schubert 0x03, 0x03, #TLSv1.2 378*e0c4386eSCy Schubert 0x00, 0x03, # Ciphersuites len 379*e0c4386eSCy Schubert 0x00, 0x00, # Session id len 380*e0c4386eSCy Schubert 0x00, 0x20, # Challenge len 381*e0c4386eSCy Schubert 0x00, 0x00, 0x2f, #AES128-SHA 382*e0c4386eSCy Schubert 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90, 383*e0c4386eSCy Schubert 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56, 384*e0c4386eSCy Schubert 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge 385*e0c4386eSCy Schubert 386*e0c4386eSCy Schubert if ($sslv2testtype == SSLV2_IN_SSLV2) { 387*e0c4386eSCy Schubert # Set the version to "real" SSLv2 388*e0c4386eSCy Schubert vec($clienthello, 1, 8) = 0x00; 389*e0c4386eSCy Schubert vec($clienthello, 2, 8) = 0x02; 390*e0c4386eSCy Schubert } 391*e0c4386eSCy Schubert 392*e0c4386eSCy Schubert my $chlen = length $clienthello; 393*e0c4386eSCy Schubert 394*e0c4386eSCy Schubert $record = TLSProxy::Record->new( 395*e0c4386eSCy Schubert 0, 396*e0c4386eSCy Schubert TLSProxy::Record::RT_HANDSHAKE, 397*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 398*e0c4386eSCy Schubert $chlen, 399*e0c4386eSCy Schubert 1, #SSLv2 400*e0c4386eSCy Schubert $chlen, 401*e0c4386eSCy Schubert $chlen, 402*e0c4386eSCy Schubert $clienthello, 403*e0c4386eSCy Schubert $clienthello 404*e0c4386eSCy Schubert ); 405*e0c4386eSCy Schubert 406*e0c4386eSCy Schubert push @{$proxy->record_list}, $record; 407*e0c4386eSCy Schubert } else { 408*e0c4386eSCy Schubert # For this test we're using a real TLS ClientHello 409*e0c4386eSCy Schubert $clienthello = 410*e0c4386eSCy Schubert pack "C49", 411*e0c4386eSCy Schubert 0x01, # ClientHello 412*e0c4386eSCy Schubert 0x00, 0x00, 0x2D, # Message length 413*e0c4386eSCy Schubert 0x03, 0x03, # TLSv1.2 414*e0c4386eSCy Schubert 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90, 415*e0c4386eSCy Schubert 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56, 416*e0c4386eSCy Schubert 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random 417*e0c4386eSCy Schubert 0x00, # Session id len 418*e0c4386eSCy Schubert 0x00, 0x04, # Ciphersuites len 419*e0c4386eSCy Schubert 0x00, 0x2f, # AES128-SHA 420*e0c4386eSCy Schubert 0x00, 0xff, # Empty reneg info SCSV 421*e0c4386eSCy Schubert 0x01, # Compression methods len 422*e0c4386eSCy Schubert 0x00, # Null compression 423*e0c4386eSCy Schubert 0x00, 0x00; # Extensions len 424*e0c4386eSCy Schubert 425*e0c4386eSCy Schubert # Split this into 3: A TLS record; a SSLv2 record and a TLS record. 426*e0c4386eSCy Schubert # We deliberately split the second record prior to the Challenge/Random 427*e0c4386eSCy Schubert # and set the first byte of the random to 1. This makes the second SSLv2 428*e0c4386eSCy Schubert # record look like an SSLv2 ClientHello 429*e0c4386eSCy Schubert my $frag1 = substr $clienthello, 0, 6; 430*e0c4386eSCy Schubert my $frag2 = substr $clienthello, 6, 32; 431*e0c4386eSCy Schubert my $frag3 = substr $clienthello, 38; 432*e0c4386eSCy Schubert 433*e0c4386eSCy Schubert my $fraglen = length $frag1; 434*e0c4386eSCy Schubert $record = TLSProxy::Record->new( 435*e0c4386eSCy Schubert 0, 436*e0c4386eSCy Schubert TLSProxy::Record::RT_HANDSHAKE, 437*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 438*e0c4386eSCy Schubert $fraglen, 439*e0c4386eSCy Schubert 0, 440*e0c4386eSCy Schubert $fraglen, 441*e0c4386eSCy Schubert $fraglen, 442*e0c4386eSCy Schubert $frag1, 443*e0c4386eSCy Schubert $frag1 444*e0c4386eSCy Schubert ); 445*e0c4386eSCy Schubert push @{$proxy->record_list}, $record; 446*e0c4386eSCy Schubert 447*e0c4386eSCy Schubert $fraglen = length $frag2; 448*e0c4386eSCy Schubert my $recvers; 449*e0c4386eSCy Schubert if ($sslv2testtype == FRAGMENTED_IN_SSLV2) { 450*e0c4386eSCy Schubert $recvers = 1; 451*e0c4386eSCy Schubert } else { 452*e0c4386eSCy Schubert $recvers = 0; 453*e0c4386eSCy Schubert } 454*e0c4386eSCy Schubert $record = TLSProxy::Record->new( 455*e0c4386eSCy Schubert 0, 456*e0c4386eSCy Schubert TLSProxy::Record::RT_HANDSHAKE, 457*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 458*e0c4386eSCy Schubert $fraglen, 459*e0c4386eSCy Schubert $recvers, 460*e0c4386eSCy Schubert $fraglen, 461*e0c4386eSCy Schubert $fraglen, 462*e0c4386eSCy Schubert $frag2, 463*e0c4386eSCy Schubert $frag2 464*e0c4386eSCy Schubert ); 465*e0c4386eSCy Schubert push @{$proxy->record_list}, $record; 466*e0c4386eSCy Schubert 467*e0c4386eSCy Schubert $fraglen = length $frag3; 468*e0c4386eSCy Schubert $record = TLSProxy::Record->new( 469*e0c4386eSCy Schubert 0, 470*e0c4386eSCy Schubert TLSProxy::Record::RT_HANDSHAKE, 471*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 472*e0c4386eSCy Schubert $fraglen, 473*e0c4386eSCy Schubert 0, 474*e0c4386eSCy Schubert $fraglen, 475*e0c4386eSCy Schubert $fraglen, 476*e0c4386eSCy Schubert $frag3, 477*e0c4386eSCy Schubert $frag3 478*e0c4386eSCy Schubert ); 479*e0c4386eSCy Schubert push @{$proxy->record_list}, $record; 480*e0c4386eSCy Schubert } 481*e0c4386eSCy Schubert 482*e0c4386eSCy Schubert} 483*e0c4386eSCy Schubert 484*e0c4386eSCy Schubertsub add_unknown_record_type 485*e0c4386eSCy Schubert{ 486*e0c4386eSCy Schubert my $proxy = shift; 487*e0c4386eSCy Schubert my $records = $proxy->record_list; 488*e0c4386eSCy Schubert state $added_record; 489*e0c4386eSCy Schubert 490*e0c4386eSCy Schubert # We'll change a record after the initial version neg has taken place 491*e0c4386eSCy Schubert if ($proxy->flight == 0) { 492*e0c4386eSCy Schubert $added_record = 0; 493*e0c4386eSCy Schubert return; 494*e0c4386eSCy Schubert } elsif ($proxy->flight != 1 || $added_record) { 495*e0c4386eSCy Schubert $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10; 496*e0c4386eSCy Schubert return; 497*e0c4386eSCy Schubert } 498*e0c4386eSCy Schubert 499*e0c4386eSCy Schubert my $record = TLSProxy::Record->new( 500*e0c4386eSCy Schubert 1, 501*e0c4386eSCy Schubert TLSProxy::Record::RT_UNKNOWN, 502*e0c4386eSCy Schubert @{$records}[-1]->version(), 503*e0c4386eSCy Schubert 1, 504*e0c4386eSCy Schubert 0, 505*e0c4386eSCy Schubert 1, 506*e0c4386eSCy Schubert 1, 507*e0c4386eSCy Schubert "X", 508*e0c4386eSCy Schubert "X" 509*e0c4386eSCy Schubert ); 510*e0c4386eSCy Schubert 511*e0c4386eSCy Schubert #Find ServerHello record and insert after that 512*e0c4386eSCy Schubert my $i; 513*e0c4386eSCy Schubert for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) { 514*e0c4386eSCy Schubert next; 515*e0c4386eSCy Schubert } 516*e0c4386eSCy Schubert $i++; 517*e0c4386eSCy Schubert 518*e0c4386eSCy Schubert splice @{$proxy->record_list}, $i, 0, $record; 519*e0c4386eSCy Schubert $added_record = 1; 520*e0c4386eSCy Schubert} 521*e0c4386eSCy Schubert 522*e0c4386eSCy Schubertsub change_version 523*e0c4386eSCy Schubert{ 524*e0c4386eSCy Schubert my $proxy = shift; 525*e0c4386eSCy Schubert my $records = $proxy->record_list; 526*e0c4386eSCy Schubert 527*e0c4386eSCy Schubert # We'll change a version after the initial version neg has taken place 528*e0c4386eSCy Schubert if ($proxy->flight != 1) { 529*e0c4386eSCy Schubert $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 70; 530*e0c4386eSCy Schubert return; 531*e0c4386eSCy Schubert } 532*e0c4386eSCy Schubert 533*e0c4386eSCy Schubert if ($#{$records} > 1) { 534*e0c4386eSCy Schubert # ... typically in ServerHelloDone 535*e0c4386eSCy Schubert @{$records}[-1]->version(TLSProxy::Record::VERS_TLS_1_1); 536*e0c4386eSCy Schubert } 537*e0c4386eSCy Schubert} 538*e0c4386eSCy Schubert 539*e0c4386eSCy Schubertsub change_outer_record_type 540*e0c4386eSCy Schubert{ 541*e0c4386eSCy Schubert my $proxy = shift; 542*e0c4386eSCy Schubert my $records = $proxy->record_list; 543*e0c4386eSCy Schubert 544*e0c4386eSCy Schubert # We'll change a record after the initial version neg has taken place 545*e0c4386eSCy Schubert if ($proxy->flight != 1) { 546*e0c4386eSCy Schubert $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10; 547*e0c4386eSCy Schubert return; 548*e0c4386eSCy Schubert } 549*e0c4386eSCy Schubert 550*e0c4386eSCy Schubert # Find CCS record and change record after that 551*e0c4386eSCy Schubert my $i = 0; 552*e0c4386eSCy Schubert foreach my $record (@{$records}) { 553*e0c4386eSCy Schubert last if $record->content_type == TLSProxy::Record::RT_CCS; 554*e0c4386eSCy Schubert $i++; 555*e0c4386eSCy Schubert } 556*e0c4386eSCy Schubert if (defined(${$records}[++$i])) { 557*e0c4386eSCy Schubert ${$records}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE); 558*e0c4386eSCy Schubert } 559*e0c4386eSCy Schubert} 560*e0c4386eSCy Schubert 561*e0c4386eSCy Schubertsub not_on_record_boundary 562*e0c4386eSCy Schubert{ 563*e0c4386eSCy Schubert my $proxy = shift; 564*e0c4386eSCy Schubert my $records = $proxy->record_list; 565*e0c4386eSCy Schubert my $data; 566*e0c4386eSCy Schubert 567*e0c4386eSCy Schubert #Find server's first flight 568*e0c4386eSCy Schubert if ($proxy->flight != 1) { 569*e0c4386eSCy Schubert $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10; 570*e0c4386eSCy Schubert return; 571*e0c4386eSCy Schubert } 572*e0c4386eSCy Schubert 573*e0c4386eSCy Schubert if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) { 574*e0c4386eSCy Schubert #Merge the ServerHello and EncryptedExtensions records into one 575*e0c4386eSCy Schubert my $i = 0; 576*e0c4386eSCy Schubert foreach my $record (@{$records}) { 577*e0c4386eSCy Schubert if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) { 578*e0c4386eSCy Schubert $record->{sent} = 1; # pretend it's sent already 579*e0c4386eSCy Schubert last; 580*e0c4386eSCy Schubert } 581*e0c4386eSCy Schubert $i++; 582*e0c4386eSCy Schubert } 583*e0c4386eSCy Schubert 584*e0c4386eSCy Schubert if (defined(${$records}[$i+1])) { 585*e0c4386eSCy Schubert $data = ${$records}[$i]->data(); 586*e0c4386eSCy Schubert $data .= ${$records}[$i+1]->decrypt_data(); 587*e0c4386eSCy Schubert ${$records}[$i+1]->data($data); 588*e0c4386eSCy Schubert ${$records}[$i+1]->len(length $data); 589*e0c4386eSCy Schubert 590*e0c4386eSCy Schubert #Delete the old ServerHello record 591*e0c4386eSCy Schubert splice @{$records}, $i, 1; 592*e0c4386eSCy Schubert } 593*e0c4386eSCy Schubert } elsif ($boundary_test_type == DATA_AFTER_FINISHED) { 594*e0c4386eSCy Schubert return if @{$proxy->{message_list}}[-1]->{mt} 595*e0c4386eSCy Schubert != TLSProxy::Message::MT_FINISHED; 596*e0c4386eSCy Schubert 597*e0c4386eSCy Schubert my $last_record = @{$records}[-1]; 598*e0c4386eSCy Schubert $data = $last_record->decrypt_data; 599*e0c4386eSCy Schubert 600*e0c4386eSCy Schubert #Add a KeyUpdate message onto the end of the Finished record 601*e0c4386eSCy Schubert my $keyupdate = pack "C5", 602*e0c4386eSCy Schubert 0x18, # KeyUpdate 603*e0c4386eSCy Schubert 0x00, 0x00, 0x01, # Message length 604*e0c4386eSCy Schubert 0x00; # Update not requested 605*e0c4386eSCy Schubert 606*e0c4386eSCy Schubert $data .= $keyupdate; 607*e0c4386eSCy Schubert 608*e0c4386eSCy Schubert #Add content type and tag 609*e0c4386eSCy Schubert $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 610*e0c4386eSCy Schubert 611*e0c4386eSCy Schubert #Update the record 612*e0c4386eSCy Schubert $last_record->data($data); 613*e0c4386eSCy Schubert $last_record->len(length $data); 614*e0c4386eSCy Schubert } elsif ($boundary_test_type == DATA_AFTER_KEY_UPDATE) { 615*e0c4386eSCy Schubert return if @{$proxy->{message_list}}[-1]->{mt} 616*e0c4386eSCy Schubert != TLSProxy::Message::MT_FINISHED; 617*e0c4386eSCy Schubert 618*e0c4386eSCy Schubert #KeyUpdates must end on a record boundary 619*e0c4386eSCy Schubert 620*e0c4386eSCy Schubert my $record = TLSProxy::Record->new( 621*e0c4386eSCy Schubert 1, 622*e0c4386eSCy Schubert TLSProxy::Record::RT_APPLICATION_DATA, 623*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 624*e0c4386eSCy Schubert 0, 625*e0c4386eSCy Schubert 0, 626*e0c4386eSCy Schubert 0, 627*e0c4386eSCy Schubert 0, 628*e0c4386eSCy Schubert "", 629*e0c4386eSCy Schubert "" 630*e0c4386eSCy Schubert ); 631*e0c4386eSCy Schubert 632*e0c4386eSCy Schubert #Add two KeyUpdate messages into a single record 633*e0c4386eSCy Schubert my $keyupdate = pack "C5", 634*e0c4386eSCy Schubert 0x18, # KeyUpdate 635*e0c4386eSCy Schubert 0x00, 0x00, 0x01, # Message length 636*e0c4386eSCy Schubert 0x00; # Update not requested 637*e0c4386eSCy Schubert 638*e0c4386eSCy Schubert $data = $keyupdate.$keyupdate; 639*e0c4386eSCy Schubert 640*e0c4386eSCy Schubert #Add content type and tag 641*e0c4386eSCy Schubert $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 642*e0c4386eSCy Schubert 643*e0c4386eSCy Schubert $record->data($data); 644*e0c4386eSCy Schubert $record->len(length $data); 645*e0c4386eSCy Schubert push @{$records}, $record; 646*e0c4386eSCy Schubert } else { 647*e0c4386eSCy Schubert return if @{$proxy->{message_list}}[-1]->{mt} 648*e0c4386eSCy Schubert != TLSProxy::Message::MT_FINISHED; 649*e0c4386eSCy Schubert 650*e0c4386eSCy Schubert my $record = TLSProxy::Record->new( 651*e0c4386eSCy Schubert 1, 652*e0c4386eSCy Schubert TLSProxy::Record::RT_APPLICATION_DATA, 653*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 654*e0c4386eSCy Schubert 0, 655*e0c4386eSCy Schubert 0, 656*e0c4386eSCy Schubert 0, 657*e0c4386eSCy Schubert 0, 658*e0c4386eSCy Schubert "", 659*e0c4386eSCy Schubert "" 660*e0c4386eSCy Schubert ); 661*e0c4386eSCy Schubert 662*e0c4386eSCy Schubert #Add a partial KeyUpdate message into the record 663*e0c4386eSCy Schubert $data = pack "C1", 664*e0c4386eSCy Schubert 0x18; # KeyUpdate message type. Omit the rest of the message header 665*e0c4386eSCy Schubert 666*e0c4386eSCy Schubert #Add content type and tag 667*e0c4386eSCy Schubert $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 668*e0c4386eSCy Schubert 669*e0c4386eSCy Schubert $record->data($data); 670*e0c4386eSCy Schubert $record->len(length $data); 671*e0c4386eSCy Schubert push @{$records}, $record; 672*e0c4386eSCy Schubert 673*e0c4386eSCy Schubert if ($boundary_test_type == DATA_BETWEEN_KEY_UPDATE) { 674*e0c4386eSCy Schubert #Now add an app data record 675*e0c4386eSCy Schubert $record = TLSProxy::Record->new( 676*e0c4386eSCy Schubert 1, 677*e0c4386eSCy Schubert TLSProxy::Record::RT_APPLICATION_DATA, 678*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 679*e0c4386eSCy Schubert 0, 680*e0c4386eSCy Schubert 0, 681*e0c4386eSCy Schubert 0, 682*e0c4386eSCy Schubert 0, 683*e0c4386eSCy Schubert "", 684*e0c4386eSCy Schubert "" 685*e0c4386eSCy Schubert ); 686*e0c4386eSCy Schubert 687*e0c4386eSCy Schubert #Add an empty app data record (just content type and tag) 688*e0c4386eSCy Schubert $data = pack("C", TLSProxy::Record::RT_APPLICATION_DATA).("\0"x16); 689*e0c4386eSCy Schubert 690*e0c4386eSCy Schubert $record->data($data); 691*e0c4386eSCy Schubert $record->len(length $data); 692*e0c4386eSCy Schubert push @{$records}, $record; 693*e0c4386eSCy Schubert } 694*e0c4386eSCy Schubert 695*e0c4386eSCy Schubert #Now add the rest of the KeyUpdate message 696*e0c4386eSCy Schubert $record = TLSProxy::Record->new( 697*e0c4386eSCy Schubert 1, 698*e0c4386eSCy Schubert TLSProxy::Record::RT_APPLICATION_DATA, 699*e0c4386eSCy Schubert TLSProxy::Record::VERS_TLS_1_2, 700*e0c4386eSCy Schubert 0, 701*e0c4386eSCy Schubert 0, 702*e0c4386eSCy Schubert 0, 703*e0c4386eSCy Schubert 0, 704*e0c4386eSCy Schubert "", 705*e0c4386eSCy Schubert "" 706*e0c4386eSCy Schubert ); 707*e0c4386eSCy Schubert 708*e0c4386eSCy Schubert #Add the last 4 bytes of the KeyUpdate record 709*e0c4386eSCy Schubert $data = pack "C4", 710*e0c4386eSCy Schubert 0x00, 0x00, 0x01, # Message length 711*e0c4386eSCy Schubert 0x00; # Update not requested 712*e0c4386eSCy Schubert 713*e0c4386eSCy Schubert #Add content type and tag 714*e0c4386eSCy Schubert $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 715*e0c4386eSCy Schubert 716*e0c4386eSCy Schubert $record->data($data); 717*e0c4386eSCy Schubert $record->len(length $data); 718*e0c4386eSCy Schubert push @{$records}, $record; 719*e0c4386eSCy Schubert 720*e0c4386eSCy Schubert } 721*e0c4386eSCy Schubert} 722