1# Copyright 2016-2024 The OpenSSL Project Authors. All Rights Reserved. 2# 3# Licensed under the Apache License 2.0 (the "License"). You may not use 4# this file except in compliance with the License. You can obtain a copy 5# in the file LICENSE in the source distribution or at 6# https://www.openssl.org/source/license.html 7 8use strict; 9 10package TLSProxy::Message; 11 12use TLSProxy::Alert; 13 14use constant TLS_MESSAGE_HEADER_LENGTH => 4; 15 16#Message types 17use constant { 18 MT_HELLO_REQUEST => 0, 19 MT_CLIENT_HELLO => 1, 20 MT_SERVER_HELLO => 2, 21 MT_NEW_SESSION_TICKET => 4, 22 MT_ENCRYPTED_EXTENSIONS => 8, 23 MT_CERTIFICATE => 11, 24 MT_SERVER_KEY_EXCHANGE => 12, 25 MT_CERTIFICATE_REQUEST => 13, 26 MT_SERVER_HELLO_DONE => 14, 27 MT_CERTIFICATE_VERIFY => 15, 28 MT_CLIENT_KEY_EXCHANGE => 16, 29 MT_FINISHED => 20, 30 MT_CERTIFICATE_STATUS => 22, 31 MT_NEXT_PROTO => 67 32}; 33 34#Alert levels 35use constant { 36 AL_LEVEL_WARN => 1, 37 AL_LEVEL_FATAL => 2 38}; 39 40#Alert descriptions 41use constant { 42 AL_DESC_CLOSE_NOTIFY => 0, 43 AL_DESC_UNEXPECTED_MESSAGE => 10, 44 AL_DESC_ILLEGAL_PARAMETER => 47, 45 AL_DESC_NO_RENEGOTIATION => 100 46}; 47 48my %message_type = ( 49 MT_HELLO_REQUEST, "HelloRequest", 50 MT_CLIENT_HELLO, "ClientHello", 51 MT_SERVER_HELLO, "ServerHello", 52 MT_NEW_SESSION_TICKET, "NewSessionTicket", 53 MT_ENCRYPTED_EXTENSIONS, "EncryptedExtensions", 54 MT_CERTIFICATE, "Certificate", 55 MT_SERVER_KEY_EXCHANGE, "ServerKeyExchange", 56 MT_CERTIFICATE_REQUEST, "CertificateRequest", 57 MT_SERVER_HELLO_DONE, "ServerHelloDone", 58 MT_CERTIFICATE_VERIFY, "CertificateVerify", 59 MT_CLIENT_KEY_EXCHANGE, "ClientKeyExchange", 60 MT_FINISHED, "Finished", 61 MT_CERTIFICATE_STATUS, "CertificateStatus", 62 MT_NEXT_PROTO, "NextProto" 63); 64 65use constant { 66 EXT_SERVER_NAME => 0, 67 EXT_MAX_FRAGMENT_LENGTH => 1, 68 EXT_STATUS_REQUEST => 5, 69 EXT_SUPPORTED_GROUPS => 10, 70 EXT_EC_POINT_FORMATS => 11, 71 EXT_SRP => 12, 72 EXT_SIG_ALGS => 13, 73 EXT_USE_SRTP => 14, 74 EXT_ALPN => 16, 75 EXT_SCT => 18, 76 EXT_PADDING => 21, 77 EXT_ENCRYPT_THEN_MAC => 22, 78 EXT_EXTENDED_MASTER_SECRET => 23, 79 EXT_SESSION_TICKET => 35, 80 EXT_KEY_SHARE => 51, 81 EXT_PSK => 41, 82 EXT_SUPPORTED_VERSIONS => 43, 83 EXT_COOKIE => 44, 84 EXT_PSK_KEX_MODES => 45, 85 EXT_POST_HANDSHAKE_AUTH => 49, 86 EXT_SIG_ALGS_CERT => 50, 87 EXT_RENEGOTIATE => 65281, 88 EXT_NPN => 13172, 89 EXT_CRYPTOPRO_BUG_EXTENSION => 0xfde8, 90 EXT_UNKNOWN => 0xfffe, 91 #Unknown extension that should appear last 92 EXT_FORCE_LAST => 0xffff 93}; 94 95# SignatureScheme of TLS 1.3 from: 96# https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-signaturescheme 97# We have to manually grab the SHA224 equivalents from the old registry 98use constant { 99 SIG_ALG_RSA_PKCS1_SHA256 => 0x0401, 100 SIG_ALG_RSA_PKCS1_SHA384 => 0x0501, 101 SIG_ALG_RSA_PKCS1_SHA512 => 0x0601, 102 SIG_ALG_ECDSA_SECP256R1_SHA256 => 0x0403, 103 SIG_ALG_ECDSA_SECP384R1_SHA384 => 0x0503, 104 SIG_ALG_ECDSA_SECP521R1_SHA512 => 0x0603, 105 SIG_ALG_RSA_PSS_RSAE_SHA256 => 0x0804, 106 SIG_ALG_RSA_PSS_RSAE_SHA384 => 0x0805, 107 SIG_ALG_RSA_PSS_RSAE_SHA512 => 0x0806, 108 SIG_ALG_ED25519 => 0x0807, 109 SIG_ALG_ED448 => 0x0808, 110 SIG_ALG_RSA_PSS_PSS_SHA256 => 0x0809, 111 SIG_ALG_RSA_PSS_PSS_SHA384 => 0x080a, 112 SIG_ALG_RSA_PSS_PSS_SHA512 => 0x080b, 113 SIG_ALG_RSA_PKCS1_SHA1 => 0x0201, 114 SIG_ALG_ECDSA_SHA1 => 0x0203, 115 SIG_ALG_DSA_SHA1 => 0x0202, 116 SIG_ALG_DSA_SHA256 => 0x0402, 117 SIG_ALG_DSA_SHA384 => 0x0502, 118 SIG_ALG_DSA_SHA512 => 0x0602, 119 OSSL_SIG_ALG_RSA_PKCS1_SHA224 => 0x0301, 120 OSSL_SIG_ALG_DSA_SHA224 => 0x0302, 121 OSSL_SIG_ALG_ECDSA_SHA224 => 0x0303 122}; 123 124use constant { 125 CIPHER_RSA_WITH_AES_128_CBC_SHA => 0x002f, 126 CIPHER_DHE_RSA_AES_128_SHA => 0x0033, 127 CIPHER_ADH_AES_128_SHA => 0x0034, 128 CIPHER_TLS13_AES_128_GCM_SHA256 => 0x1301, 129 CIPHER_TLS13_AES_256_GCM_SHA384 => 0x1302 130}; 131 132use constant { 133 CLIENT => 0, 134 SERVER => 1 135}; 136 137my $payload = ""; 138my $messlen = -1; 139my $mt; 140my $startoffset = -1; 141my $server = 0; 142my $success = 0; 143my $end = 0; 144my @message_rec_list = (); 145my @message_frag_lens = (); 146my $ciphersuite = 0; 147my $successondata = 0; 148my $alert; 149 150sub clear 151{ 152 $payload = ""; 153 $messlen = -1; 154 $startoffset = -1; 155 $server = 0; 156 $success = 0; 157 $end = 0; 158 $successondata = 0; 159 @message_rec_list = (); 160 @message_frag_lens = (); 161 $alert = undef; 162} 163 164#Class method to extract messages from a record 165sub get_messages 166{ 167 my $class = shift; 168 my $serverin = shift; 169 my $record = shift; 170 my @messages = (); 171 my $message; 172 173 @message_frag_lens = (); 174 175 if ($serverin != $server && length($payload) != 0) { 176 die "Changed peer, but we still have fragment data\n"; 177 } 178 $server = $serverin; 179 180 if ($record->content_type == TLSProxy::Record::RT_CCS) { 181 if ($payload ne "") { 182 #We can't handle this yet 183 die "CCS received before message data complete\n"; 184 } 185 if (!TLSProxy::Proxy->is_tls13()) { 186 if ($server) { 187 TLSProxy::Record->server_encrypting(1); 188 } else { 189 TLSProxy::Record->client_encrypting(1); 190 } 191 } 192 } elsif ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) { 193 if ($record->len == 0 || $record->len_real == 0) { 194 print " Message truncated\n"; 195 } else { 196 my $recoffset = 0; 197 198 if (length $payload > 0) { 199 #We are continuing processing a message started in a previous 200 #record. Add this record to the list associated with this 201 #message 202 push @message_rec_list, $record; 203 204 if ($messlen <= length($payload)) { 205 #Shouldn't happen 206 die "Internal error: invalid messlen: ".$messlen 207 ." payload length:".length($payload)."\n"; 208 } 209 if (length($payload) + $record->decrypt_len >= $messlen) { 210 #We can complete the message with this record 211 $recoffset = $messlen - length($payload); 212 $payload .= substr($record->decrypt_data, 0, $recoffset); 213 push @message_frag_lens, $recoffset; 214 $message = create_message($server, $mt, $payload, 215 $startoffset); 216 push @messages, $message; 217 218 $payload = ""; 219 } else { 220 #This is just part of the total message 221 $payload .= $record->decrypt_data; 222 $recoffset = $record->decrypt_len; 223 push @message_frag_lens, $record->decrypt_len; 224 } 225 print " Partial message data read: ".$recoffset." bytes\n"; 226 } 227 228 while ($record->decrypt_len > $recoffset) { 229 #We are at the start of a new message 230 if ($record->decrypt_len - $recoffset < 4) { 231 #Whilst technically probably valid we can't cope with this 232 die "End of record in the middle of a message header\n"; 233 } 234 @message_rec_list = ($record); 235 my $lenhi; 236 my $lenlo; 237 ($mt, $lenhi, $lenlo) = unpack('CnC', 238 substr($record->decrypt_data, 239 $recoffset)); 240 $messlen = ($lenhi << 8) | $lenlo; 241 print " Message type: $message_type{$mt}\n"; 242 print " Message Length: $messlen\n"; 243 $startoffset = $recoffset; 244 $recoffset += 4; 245 $payload = ""; 246 247 if ($recoffset <= $record->decrypt_len) { 248 #Some payload data is present in this record 249 if ($record->decrypt_len - $recoffset >= $messlen) { 250 #We can complete the message with this record 251 $payload .= substr($record->decrypt_data, $recoffset, 252 $messlen); 253 $recoffset += $messlen; 254 push @message_frag_lens, $messlen; 255 $message = create_message($server, $mt, $payload, 256 $startoffset); 257 push @messages, $message; 258 259 $payload = ""; 260 } else { 261 #This is just part of the total message 262 $payload .= substr($record->decrypt_data, $recoffset, 263 $record->decrypt_len - $recoffset); 264 $recoffset = $record->decrypt_len; 265 push @message_frag_lens, $recoffset; 266 } 267 } 268 } 269 } 270 } elsif ($record->content_type == TLSProxy::Record::RT_APPLICATION_DATA) { 271 print " [ENCRYPTED APPLICATION DATA]\n"; 272 print " [".$record->decrypt_data."]\n"; 273 274 if ($successondata) { 275 $success = 1; 276 $end = 1; 277 } 278 } elsif ($record->content_type == TLSProxy::Record::RT_ALERT) { 279 my ($alertlev, $alertdesc) = unpack('CC', $record->decrypt_data); 280 print " [$alertlev, $alertdesc]\n"; 281 #A CloseNotify from the client indicates we have finished successfully 282 #(we assume) 283 if (!$end && !$server && $alertlev == AL_LEVEL_WARN 284 && $alertdesc == AL_DESC_CLOSE_NOTIFY) { 285 $success = 1; 286 } 287 #Fatal or close notify alerts end the test 288 if ($alertlev == AL_LEVEL_FATAL || $alertdesc == AL_DESC_CLOSE_NOTIFY) { 289 $end = 1; 290 } 291 $alert = TLSProxy::Alert->new( 292 $server, 293 $record->encrypted, 294 $alertlev, 295 $alertdesc); 296 } 297 298 return @messages; 299} 300 301#Function to work out which sub-class we need to create and then 302#construct it 303sub create_message 304{ 305 my ($server, $mt, $data, $startoffset) = @_; 306 my $message; 307 308 #We only support ClientHello in this version...needs to be extended for 309 #others 310 if ($mt == MT_CLIENT_HELLO) { 311 $message = TLSProxy::ClientHello->new( 312 $server, 313 $data, 314 [@message_rec_list], 315 $startoffset, 316 [@message_frag_lens] 317 ); 318 $message->parse(); 319 } elsif ($mt == MT_SERVER_HELLO) { 320 $message = TLSProxy::ServerHello->new( 321 $server, 322 $data, 323 [@message_rec_list], 324 $startoffset, 325 [@message_frag_lens] 326 ); 327 $message->parse(); 328 } elsif ($mt == MT_ENCRYPTED_EXTENSIONS) { 329 $message = TLSProxy::EncryptedExtensions->new( 330 $server, 331 $data, 332 [@message_rec_list], 333 $startoffset, 334 [@message_frag_lens] 335 ); 336 $message->parse(); 337 } elsif ($mt == MT_CERTIFICATE) { 338 $message = TLSProxy::Certificate->new( 339 $server, 340 $data, 341 [@message_rec_list], 342 $startoffset, 343 [@message_frag_lens] 344 ); 345 $message->parse(); 346 } elsif ($mt == MT_CERTIFICATE_REQUEST) { 347 $message = TLSProxy::CertificateRequest->new( 348 $server, 349 $data, 350 [@message_rec_list], 351 $startoffset, 352 [@message_frag_lens] 353 ); 354 $message->parse(); 355 } elsif ($mt == MT_CERTIFICATE_VERIFY) { 356 $message = TLSProxy::CertificateVerify->new( 357 $server, 358 $data, 359 [@message_rec_list], 360 $startoffset, 361 [@message_frag_lens] 362 ); 363 $message->parse(); 364 } elsif ($mt == MT_SERVER_KEY_EXCHANGE) { 365 $message = TLSProxy::ServerKeyExchange->new( 366 $server, 367 $data, 368 [@message_rec_list], 369 $startoffset, 370 [@message_frag_lens] 371 ); 372 $message->parse(); 373 } elsif ($mt == MT_NEW_SESSION_TICKET) { 374 $message = TLSProxy::NewSessionTicket->new( 375 $server, 376 $data, 377 [@message_rec_list], 378 $startoffset, 379 [@message_frag_lens] 380 ); 381 $message->parse(); 382 } elsif ($mt == MT_NEXT_PROTO) { 383 $message = TLSProxy::NextProto->new( 384 $server, 385 $data, 386 [@message_rec_list], 387 $startoffset, 388 [@message_frag_lens] 389 ); 390 $message->parse(); 391 } else { 392 #Unknown message type 393 $message = TLSProxy::Message->new( 394 $server, 395 $mt, 396 $data, 397 [@message_rec_list], 398 $startoffset, 399 [@message_frag_lens] 400 ); 401 } 402 403 return $message; 404} 405 406sub end 407{ 408 my $class = shift; 409 return $end; 410} 411sub success 412{ 413 my $class = shift; 414 return $success; 415} 416sub fail 417{ 418 my $class = shift; 419 return !$success && $end; 420} 421 422sub alert 423{ 424 return $alert; 425} 426 427sub new 428{ 429 my $class = shift; 430 my ($server, 431 $mt, 432 $data, 433 $records, 434 $startoffset, 435 $message_frag_lens) = @_; 436 437 my $self = { 438 server => $server, 439 data => $data, 440 records => $records, 441 mt => $mt, 442 startoffset => $startoffset, 443 message_frag_lens => $message_frag_lens, 444 dupext => -1 445 }; 446 447 return bless $self, $class; 448} 449 450sub ciphersuite 451{ 452 my $class = shift; 453 if (@_) { 454 $ciphersuite = shift; 455 } 456 return $ciphersuite; 457} 458 459#Update all the underlying records with the modified data from this message 460#Note: Only supports TLSv1.3 and ETM encryption 461sub repack 462{ 463 my $self = shift; 464 my $msgdata; 465 466 my $numrecs = $#{$self->records}; 467 468 $self->set_message_contents(); 469 470 my $lenhi; 471 my $lenlo; 472 473 $lenlo = length($self->data) & 0xff; 474 $lenhi = length($self->data) >> 8; 475 $msgdata = pack('CnC', $self->mt, $lenhi, $lenlo).$self->data; 476 477 if ($numrecs == 0) { 478 #The message is fully contained within one record 479 my ($rec) = @{$self->records}; 480 my $recdata = $rec->decrypt_data; 481 482 my $old_length; 483 484 # We use empty message_frag_lens to indicates that pre-repacking, 485 # the message wasn't present. The first fragment length doesn't include 486 # the TLS header, so we need to check and compute the right length. 487 if (@{$self->message_frag_lens}) { 488 $old_length = ${$self->message_frag_lens}[0] + 489 TLS_MESSAGE_HEADER_LENGTH; 490 } else { 491 $old_length = 0; 492 } 493 494 my $prefix = substr($recdata, 0, $self->startoffset); 495 my $suffix = substr($recdata, $self->startoffset + $old_length); 496 497 $rec->decrypt_data($prefix.($msgdata).($suffix)); 498 # TODO(openssl-team): don't keep explicit lengths. 499 # (If a length override is ever needed to construct invalid packets, 500 # use an explicit override field instead.) 501 $rec->decrypt_len(length($rec->decrypt_data)); 502 # Only support re-encryption for TLSv1.3 and ETM. 503 if ($rec->encrypted()) { 504 if (TLSProxy::Proxy->is_tls13()) { 505 #Add content type (1 byte) and 16 tag bytes 506 $rec->data($rec->decrypt_data 507 .pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16)); 508 } elsif ($rec->etm()) { 509 my $data = $rec->decrypt_data; 510 #Add padding 511 my $padval = length($data) % 16; 512 $padval = 15 - $padval; 513 for (0..$padval) { 514 $data .= pack("C", $padval); 515 } 516 517 #Add MAC. Assumed to be 20 bytes 518 foreach my $macval (0..19) { 519 $data .= pack("C", $macval); 520 } 521 522 if ($rec->version() >= TLSProxy::Record::VERS_TLS_1_1) { 523 #Explicit IV 524 $data = ("\0"x16).$data; 525 } 526 $rec->data($data); 527 } else { 528 die "Unsupported encryption: No ETM"; 529 } 530 } else { 531 $rec->data($rec->decrypt_data); 532 } 533 $rec->len(length($rec->data)); 534 535 #Update the fragment len in case we changed it above 536 ${$self->message_frag_lens}[0] = length($msgdata) 537 - TLS_MESSAGE_HEADER_LENGTH; 538 return; 539 } 540 541 #Note we don't currently support changing a fragmented message length 542 my $recctr = 0; 543 my $datadone = 0; 544 foreach my $rec (@{$self->records}) { 545 my $recdata = $rec->decrypt_data; 546 if ($recctr == 0) { 547 #This is the first record 548 my $remainlen = length($recdata) - $self->startoffset; 549 $rec->data(substr($recdata, 0, $self->startoffset) 550 .substr(($msgdata), 0, $remainlen)); 551 $datadone += $remainlen; 552 } elsif ($recctr + 1 == $numrecs) { 553 #This is the last record 554 $rec->data(substr($msgdata, $datadone)); 555 } else { 556 #This is a middle record 557 $rec->data(substr($msgdata, $datadone, length($rec->data))); 558 $datadone += length($rec->data); 559 } 560 $recctr++; 561 } 562} 563 564#To be overridden by sub-classes 565sub set_message_contents 566{ 567} 568 569#Read only accessors 570sub server 571{ 572 my $self = shift; 573 return $self->{server}; 574} 575 576#Read/write accessors 577sub mt 578{ 579 my $self = shift; 580 if (@_) { 581 $self->{mt} = shift; 582 } 583 return $self->{mt}; 584} 585sub data 586{ 587 my $self = shift; 588 if (@_) { 589 $self->{data} = shift; 590 } 591 return $self->{data}; 592} 593sub records 594{ 595 my $self = shift; 596 if (@_) { 597 $self->{records} = shift; 598 } 599 return $self->{records}; 600} 601sub startoffset 602{ 603 my $self = shift; 604 if (@_) { 605 $self->{startoffset} = shift; 606 } 607 return $self->{startoffset}; 608} 609sub message_frag_lens 610{ 611 my $self = shift; 612 if (@_) { 613 $self->{message_frag_lens} = shift; 614 } 615 return $self->{message_frag_lens}; 616} 617sub encoded_length 618{ 619 my $self = shift; 620 return TLS_MESSAGE_HEADER_LENGTH + length($self->data); 621} 622sub dupext 623{ 624 my $self = shift; 625 if (@_) { 626 $self->{dupext} = shift; 627 } 628 return $self->{dupext}; 629} 630sub successondata 631{ 632 my $class = shift; 633 if (@_) { 634 $successondata = shift; 635 } 636 return $successondata; 637} 6381; 639