1#! /usr/bin/env perl 2# Copyright 2016-2025 The OpenSSL Project Authors. All Rights Reserved. 3# 4# Licensed under the Apache License 2.0 (the "License"). You may not use 5# this file except in compliance with the License. You can obtain a copy 6# in the file LICENSE in the source distribution or at 7# https://www.openssl.org/source/license.html 8 9use strict; 10use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/; 11use OpenSSL::Test::Utils; 12use TLSProxy::Proxy; 13 14my $test_name = "test_sslsigalgs"; 15setup($test_name); 16 17plan skip_all => "TLSProxy isn't usable on $^O" 18 if $^O =~ /^(VMS)$/; 19 20plan skip_all => "$test_name needs the dynamic engine feature enabled" 21 if disabled("engine") || disabled("dynamic-engine"); 22 23plan skip_all => "$test_name needs the sock feature enabled" 24 if disabled("sock"); 25 26plan skip_all => "$test_name needs TLS1.2 or TLS1.3 enabled" 27 if disabled("tls1_2") && disabled("tls1_3"); 28 29my $proxy = TLSProxy::Proxy->new( 30 undef, 31 cmdstr(app(["openssl"]), display => 1), 32 srctop_file("apps", "server.pem"), 33 (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE}) 34); 35 36use constant { 37 NO_SIG_ALGS_EXT => 0, 38 EMPTY_SIG_ALGS_EXT => 1, 39 NO_KNOWN_SIG_ALGS => 2, 40 NO_PSS_SIG_ALGS => 3, 41 PSS_ONLY_SIG_ALGS => 4, 42 PURE_SIGALGS => 5, 43 COMPAT_SIGALGS => 6, 44 SIGALGS_CERT_ALL => 7, 45 SIGALGS_CERT_PKCS => 8, 46 SIGALGS_CERT_INVALID => 9, 47 UNRECOGNIZED_SIGALGS_CERT => 10, 48 UNRECOGNIZED_SIGALG => 11 49}; 50 51srand(70); 52sub randcase { 53 my ($names) = @_; 54 my @ret; 55 foreach my $name (split(/:/, $names)) { 56 my ($alg, $rest) = split(/(?=[+])/, $name, 2); 57 $alg =~ s{([a-zA-Z])}{chr(ord($1)^(int(rand(2.0)) * 32))}eg; 58 push @ret, $alg . ($rest // ""); 59 } 60 return join(":", @ret); 61} 62 63#Note: Throughout this test we override the default ciphersuites where TLSv1.2 64# is expected to ensure that a ServerKeyExchange message is sent that uses 65# the sigalgs 66 67#Test 1: Default sig algs should succeed 68$proxy->clientflags("-no_tls1_3") if disabled("ec") && disabled("dh"); 69$proxy->start() or plan skip_all => "Unable to start up Proxy for tests"; 70plan tests => 26; 71ok(TLSProxy::Message->success, "Default sigalgs"); 72my $testtype; 73 74SKIP: { 75 skip "TLSv1.3 disabled", 6 76 if disabled("tls1_3") || (disabled("ec") && disabled("dh")); 77 78 $proxy->filter(\&sigalgs_filter); 79 80 #Test 2: Sending no sig algs extension in TLSv1.3 should fail 81 $proxy->clear(); 82 $testtype = NO_SIG_ALGS_EXT; 83 $proxy->start(); 84 ok(TLSProxy::Message->fail, "No TLSv1.3 sigalgs"); 85 86 #Test 3: Sending an empty sig algs extension in TLSv1.3 should fail 87 $proxy->clear(); 88 $testtype = EMPTY_SIG_ALGS_EXT; 89 $proxy->start(); 90 ok(TLSProxy::Message->fail, "Empty TLSv1.3 sigalgs"); 91 92 #Test 4: Sending a list with no recognised sig algs in TLSv1.3 should fail 93 $proxy->clear(); 94 $testtype = NO_KNOWN_SIG_ALGS; 95 $proxy->start(); 96 ok(TLSProxy::Message->fail, "No known TLSv1.3 sigalgs"); 97 98 #Test 5: Sending a sig algs list without pss for an RSA cert in TLSv1.3 99 # should fail 100 $proxy->clear(); 101 $testtype = NO_PSS_SIG_ALGS; 102 $proxy->start(); 103 ok(TLSProxy::Message->fail, "No PSS TLSv1.3 sigalgs"); 104 105 #Test 6: Sending only TLSv1.3 PSS sig algs in TLSv1.3 should succeed 106 #TODO(TLS1.3): Do we need to verify the cert to make sure its a PSS only 107 #cert in this case? 108 $proxy->clear(); 109 $testtype = PSS_ONLY_SIG_ALGS; 110 $proxy->start(); 111 ok(TLSProxy::Message->success, "PSS only sigalgs in TLSv1.3"); 112 113 #Test 7: Modify the CertificateVerify sigalg from rsa_pss_rsae_sha256 to 114 # rsa_pss_pss_sha256. This should fail because the public key OID 115 # in the certificate is rsaEncryption and not rsassaPss 116 $proxy->filter(\&modify_cert_verify_sigalg); 117 $proxy->clear(); 118 $proxy->start(); 119 ok(TLSProxy::Message->fail, 120 "Mismatch between CertVerify sigalg and public key OID"); 121} 122 123SKIP: { 124 skip "EC or TLSv1.3 disabled", 1 125 if disabled("tls1_3") || disabled("ec"); 126 #Test 8: Sending a valid sig algs list but not including a sig type that 127 # matches the certificate should fail in TLSv1.3. 128 $proxy->clear(); 129 $proxy->clientflags("-sigalgs ".randcase("ECDSA+SHA256")); 130 $proxy->filter(undef); 131 $proxy->start(); 132 ok(TLSProxy::Message->fail, "No matching TLSv1.3 sigalgs"); 133} 134 135SKIP: { 136 skip "EC, TLSv1.3 or TLSv1.2 disabled", 1 137 if disabled("tls1_2") || disabled("tls1_3") || disabled("ec"); 138 139 #Test 9: Sending a full list of TLSv1.3 sig algs but negotiating TLSv1.2 140 # should succeed 141 $proxy->clear(); 142 $proxy->serverflags("-no_tls1_3"); 143 $proxy->ciphers("ECDHE-RSA-AES128-SHA"); 144 $proxy->filter(undef); 145 $proxy->start(); 146 ok(TLSProxy::Message->success, "TLSv1.3 client TLSv1.2 server"); 147} 148 149SKIP: { 150 skip "EC or TLSv1.2 disabled", 10 if disabled("tls1_2") || disabled("ec"); 151 152 $proxy->filter(\&sigalgs_filter); 153 154 #Test 10: Sending no sig algs extension in TLSv1.2 will make it use 155 # SHA1, which is only supported at security level 0. 156 $proxy->clear(); 157 $testtype = NO_SIG_ALGS_EXT; 158 $proxy->clientflags("-no_tls1_3 -cipher DEFAULT:\@SECLEVEL=0"); 159 $proxy->ciphers("ECDHE-RSA-AES128-SHA:\@SECLEVEL=0"); 160 $proxy->start(); 161 ok(TLSProxy::Message->success, "No TLSv1.2 sigalgs seclevel 0"); 162 163 #Test 11: Sending no sig algs extension in TLSv1.2 should fail at security 164 # level 1 since it will try to use SHA1. Testing client at level 0, 165 # server level 1. 166 $proxy->clear(); 167 $testtype = NO_SIG_ALGS_EXT; 168 $proxy->clientflags("-tls1_2 -cipher DEFAULT:\@SECLEVEL=0"); 169 $proxy->ciphers("DEFAULT:\@SECLEVEL=1"); 170 $proxy->start(); 171 ok(TLSProxy::Message->fail, "No TLSv1.2 sigalgs server seclevel 1"); 172 173 #Test 12: Sending no sig algs extension in TLSv1.2 should fail at security 174 # level 1 since it will try to use SHA1. Testing client at level 1, 175 # server level 0. 176 $proxy->clear(); 177 $testtype = NO_SIG_ALGS_EXT; 178 $proxy->clientflags("-tls1_2 -cipher DEFAULT:\@SECLEVEL=1"); 179 $proxy->ciphers("DEFAULT:\@SECLEVEL=0"); 180 $proxy->start(); 181 ok(TLSProxy::Message->fail, "No TLSv1.2 sigalgs client seclevel 2"); 182 183 #Test 13: Sending an empty sig algs extension in TLSv1.2 should fail 184 $proxy->clear(); 185 $testtype = EMPTY_SIG_ALGS_EXT; 186 $proxy->clientflags("-no_tls1_3"); 187 $proxy->ciphers("ECDHE-RSA-AES128-SHA"); 188 $proxy->start(); 189 ok(TLSProxy::Message->fail, "Empty TLSv1.2 sigalgs"); 190 191 #Test 14: Sending a list with no recognised sig algs in TLSv1.2 should fail 192 $proxy->clear(); 193 $testtype = NO_KNOWN_SIG_ALGS; 194 $proxy->clientflags("-no_tls1_3"); 195 $proxy->ciphers("ECDHE-RSA-AES128-SHA"); 196 $proxy->start(); 197 ok(TLSProxy::Message->fail, "No known TLSv1.3 sigalgs"); 198 199 #Test 15: Sending a sig algs list without pss for an RSA cert in TLSv1.2 200 # should succeed 201 $proxy->clear(); 202 $testtype = NO_PSS_SIG_ALGS; 203 $proxy->clientflags("-no_tls1_3"); 204 $proxy->ciphers("ECDHE-RSA-AES128-SHA"); 205 $proxy->start(); 206 ok(TLSProxy::Message->success, "No PSS TLSv1.2 sigalgs"); 207 208 #Test 16: Sending only TLSv1.3 PSS sig algs in TLSv1.2 should succeed 209 $proxy->clear(); 210 $testtype = PSS_ONLY_SIG_ALGS; 211 $proxy->serverflags("-no_tls1_3"); 212 $proxy->ciphers("ECDHE-RSA-AES128-SHA"); 213 $proxy->start(); 214 ok(TLSProxy::Message->success, "PSS only sigalgs in TLSv1.2"); 215 216 #Test 17: Responding with a sig alg we did not send in TLSv1.2 should fail 217 # We send rsa_pkcs1_sha256 and respond with rsa_pss_rsae_sha256 218 # TODO(TLS1.3): Add a similar test to the TLSv1.3 section above 219 # when we have an API capable of configuring the TLSv1.3 sig algs 220 $proxy->clear(); 221 $testtype = PSS_ONLY_SIG_ALGS; 222 $proxy->clientflags("-no_tls1_3 -sigalgs ".randcase("RSA+SHA256")); 223 $proxy->ciphers("ECDHE-RSA-AES128-SHA"); 224 $proxy->start(); 225 ok(TLSProxy::Message->fail, "Sigalg we did not send in TLSv1.2"); 226 227 #Test 18: Sending a valid sig algs list but not including a sig type that 228 # matches the certificate should fail in TLSv1.2 229 $proxy->clear(); 230 $proxy->clientflags("-no_tls1_3 -sigalgs ".randcase("ECDSA+SHA256")); 231 $proxy->ciphers("ECDHE-RSA-AES128-SHA"); 232 $proxy->filter(undef); 233 $proxy->start(); 234 ok(TLSProxy::Message->fail, "No matching TLSv1.2 sigalgs"); 235 $proxy->filter(\&sigalgs_filter); 236 237 #Test 19: No sig algs extension, ECDSA cert, will use SHA1, 238 # TLSv1.2 should succeed at security level 0 239 $proxy->clear(); 240 $testtype = NO_SIG_ALGS_EXT; 241 $proxy->clientflags("-no_tls1_3 -cipher DEFAULT:\@SECLEVEL=0"); 242 $proxy->serverflags("-cert " . srctop_file("test", "certs", 243 "server-ecdsa-cert.pem") . 244 " -key " . srctop_file("test", "certs", 245 "server-ecdsa-key.pem")), 246 $proxy->ciphers("ECDHE-ECDSA-AES128-SHA:\@SECLEVEL=0"); 247 $proxy->start(); 248 ok(TLSProxy::Message->success, "No TLSv1.2 sigalgs, ECDSA"); 249} 250 251my ($dsa_status, $sha1_status, $sha224_status); 252SKIP: { 253 skip "TLSv1.3 disabled", 2 254 if disabled("tls1_3") 255 || disabled("dsa") 256 || (disabled("ec") && disabled("dh")); 257 #Test 20: signature_algorithms with 1.3-only ClientHello 258 $testtype = PURE_SIGALGS; 259 $dsa_status = $sha1_status = $sha224_status = 0; 260 $proxy->clear(); 261 $proxy->clientflags("-tls1_3"); 262 $proxy->filter(\&modify_sigalgs_filter); 263 $proxy->start(); 264 ok($dsa_status && $sha1_status && $sha224_status, 265 "DSA and SHA1 sigalgs not sent for 1.3-only ClientHello"); 266 267 #Test 21: signature_algorithms with backwards compatible ClientHello 268 SKIP: { 269 skip "TLSv1.2 disabled", 1 if disabled("tls1_2"); 270 $testtype = COMPAT_SIGALGS; 271 $dsa_status = $sha1_status = $sha224_status = 0; 272 $proxy->clear(); 273 $proxy->clientflags("-cipher AES128-SHA\@SECLEVEL=0"); 274 $proxy->filter(\&modify_sigalgs_filter); 275 $proxy->start(); 276 ok($dsa_status && $sha1_status && $sha224_status, 277 "backwards compatible sigalg sent for compat ClientHello"); 278 } 279} 280 281SKIP: { 282 skip "TLSv1.3 disabled", 5 283 if disabled("tls1_3") || (disabled("ec") && disabled("dh")); 284 #Test 22: Insert signature_algorithms_cert that match normal sigalgs 285 $testtype = SIGALGS_CERT_ALL; 286 $proxy->clear(); 287 $proxy->filter(\&modify_sigalgs_cert_filter); 288 $proxy->start(); 289 ok(TLSProxy::Message->success, "sigalgs_cert in TLSv1.3"); 290 291 #Test 23: Insert signature_algorithms_cert that forces PKCS#1 cert 292 $testtype = SIGALGS_CERT_PKCS; 293 $proxy->clear(); 294 $proxy->filter(\&modify_sigalgs_cert_filter); 295 $proxy->start(); 296 ok(TLSProxy::Message->success, "sigalgs_cert in TLSv1.3 with PKCS#1 cert"); 297 298 #Test 24: Insert signature_algorithms_cert that fails 299 $testtype = SIGALGS_CERT_INVALID; 300 $proxy->clear(); 301 $proxy->filter(\&modify_sigalgs_cert_filter); 302 $proxy->start(); 303 ok(TLSProxy::Message->fail, "No matching certificate for sigalgs_cert"); 304 305 #Test 25: Send an unrecognized signature_algorithms_cert 306 # We should be able to skip over the unrecognized value and use a 307 # valid one that appears later in the list. 308 $proxy->clear(); 309 $proxy->filter(\&inject_unrecognized_sigalg); 310 $proxy->clientflags("-tls1_3"); 311 # Use -xcert to get SSL_check_chain() to run in the cert_cb. This is 312 # needed to trigger (e.g.) CVE-2020-1967 313 $proxy->serverflags("" . 314 " -xcert " . srctop_file("test", "certs", "servercert.pem") . 315 " -xkey " . srctop_file("test", "certs", "serverkey.pem") . 316 " -xchain " . srctop_file("test", "certs", "rootcert.pem")); 317 $testtype = UNRECOGNIZED_SIGALGS_CERT; 318 $proxy->start(); 319 ok(TLSProxy::Message->success(), "Unrecognized sigalg_cert in ClientHello"); 320 321 #Test 26: Send an unrecognized signature_algorithms 322 # We should be able to skip over the unrecognized value and use a 323 # valid one that appears later in the list. 324 $proxy->clear(); 325 $proxy->filter(\&inject_unrecognized_sigalg); 326 $proxy->clientflags("-tls1_3"); 327 $proxy->serverflags("" . 328 " -xcert " . srctop_file("test", "certs", "servercert.pem") . 329 " -xkey " . srctop_file("test", "certs", "serverkey.pem") . 330 " -xchain " . srctop_file("test", "certs", "rootcert.pem")); 331 $testtype = UNRECOGNIZED_SIGALG; 332 $proxy->start(); 333 ok(TLSProxy::Message->success(), "Unrecognized sigalg in ClientHello"); 334} 335 336 337 338sub sigalgs_filter 339{ 340 my $proxy = shift; 341 342 # We're only interested in the initial ClientHello 343 if ($proxy->flight != 0) { 344 return; 345 } 346 347 foreach my $message (@{$proxy->message_list}) { 348 if ($message->mt == TLSProxy::Message::MT_CLIENT_HELLO) { 349 if ($testtype == NO_SIG_ALGS_EXT) { 350 $message->delete_extension(TLSProxy::Message::EXT_SIG_ALGS); 351 } else { 352 my $sigalg; 353 if ($testtype == EMPTY_SIG_ALGS_EXT) { 354 $sigalg = pack "C2", 0x00, 0x00; 355 } elsif ($testtype == NO_KNOWN_SIG_ALGS) { 356 $sigalg = pack "C4", 0x00, 0x02, 0xff, 0xff; 357 } elsif ($testtype == NO_PSS_SIG_ALGS) { 358 #No PSS sig algs - just send rsa_pkcs1_sha256 359 $sigalg = pack "C4", 0x00, 0x02, 0x04, 0x01; 360 } else { 361 #PSS sig algs only - just send rsa_pss_rsae_sha256 362 $sigalg = pack "C4", 0x00, 0x02, 0x08, 0x04; 363 } 364 $message->set_extension(TLSProxy::Message::EXT_SIG_ALGS, $sigalg); 365 } 366 367 $message->repack(); 368 } 369 } 370} 371 372sub modify_sigalgs_filter 373{ 374 my $proxy = shift; 375 376 # We're only interested in the initial ClientHello 377 return if ($proxy->flight != 0); 378 379 foreach my $message (@{$proxy->message_list}) { 380 my $ext; 381 my @algs; 382 383 if ($message->mt == TLSProxy::Message::MT_CLIENT_HELLO) { 384 if ($testtype == PURE_SIGALGS) { 385 my $ok = 1; 386 $ext = $message->extension_data->{TLSProxy::Message::EXT_SIG_ALGS}; 387 @algs = unpack('S>*', $ext); 388 # unpack will unpack the length as well 389 shift @algs; 390 foreach (@algs) { 391 if ($_ == TLSProxy::Message::SIG_ALG_DSA_SHA256 392 || $_ == TLSProxy::Message::SIG_ALG_DSA_SHA384 393 || $_ == TLSProxy::Message::SIG_ALG_DSA_SHA512 394 || $_ == TLSProxy::Message::OSSL_SIG_ALG_DSA_SHA224 395 || $_ == TLSProxy::Message::SIG_ALG_RSA_PKCS1_SHA1 396 || $_ == TLSProxy::Message::SIG_ALG_DSA_SHA1 397 || $_ == TLSProxy::Message::SIG_ALG_ECDSA_SHA1) { 398 $ok = 0; 399 } 400 } 401 $sha1_status = $dsa_status = $sha224_status = 1 if ($ok); 402 } elsif ($testtype == COMPAT_SIGALGS) { 403 $ext = $message->extension_data->{TLSProxy::Message::EXT_SIG_ALGS}; 404 @algs = unpack('S>*', $ext); 405 # unpack will unpack the length as well 406 shift @algs; 407 foreach (@algs) { 408 if ($_ == TLSProxy::Message::SIG_ALG_DSA_SHA256 409 || $_ == TLSProxy::Message::SIG_ALG_DSA_SHA384 410 || $_ == TLSProxy::Message::SIG_ALG_DSA_SHA512) { 411 $dsa_status = 1; 412 } 413 if ($_ == TLSProxy::Message::SIG_ALG_RSA_PKCS1_SHA1 414 || $_ == TLSProxy::Message::SIG_ALG_DSA_SHA1 415 || $_ == TLSProxy::Message::SIG_ALG_ECDSA_SHA1) { 416 $sha1_status = 1; 417 } 418 if ($_ == TLSProxy::Message::OSSL_SIG_ALG_RSA_PKCS1_SHA224 419 || $_ == TLSProxy::Message::OSSL_SIG_ALG_DSA_SHA224 420 || $_ == TLSProxy::Message::OSSL_SIG_ALG_ECDSA_SHA224) { 421 $sha224_status = 1; 422 } 423 } 424 } 425 } 426 } 427} 428 429sub modify_sigalgs_cert_filter 430{ 431 my $proxy = shift; 432 433 # We're only interested in the initial ClientHello 434 if ($proxy->flight != 0) { 435 return; 436 } 437 438 foreach my $message (@{$proxy->message_list}) { 439 if ($message->mt == TLSProxy::Message::MT_CLIENT_HELLO) { 440 my $sigs; 441 # two byte length at front of sigs, then two-byte sigschemes 442 if ($testtype == SIGALGS_CERT_ALL) { 443 $sigs = pack "C26", 0x00, 0x18, 444 # rsa_pkcs_sha{256,512} rsa_pss_rsae_sha{256,512} 445 0x04, 0x01, 0x06, 0x01, 0x08, 0x04, 0x08, 0x06, 446 # ed25518 ed448 rsa_pss_pss_sha{256,512} 447 0x08, 0x07, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0b, 448 # ecdsa_secp{256,512} rsa+sha1 ecdsa+sha1 449 0x04, 0x03, 0x06, 0x03, 0x02, 0x01, 0x02, 0x03; 450 } elsif ($testtype == SIGALGS_CERT_PKCS) { 451 $sigs = pack "C10", 0x00, 0x08, 452 # rsa_pkcs_sha{256,384,512,1} 453 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x01; 454 } elsif ($testtype == SIGALGS_CERT_INVALID) { 455 $sigs = pack "C4", 0x00, 0x02, 456 # unregistered codepoint 457 0xb2, 0x6f; 458 } 459 $message->set_extension(TLSProxy::Message::EXT_SIG_ALGS_CERT, $sigs); 460 $message->repack(); 461 } 462 } 463} 464 465sub modify_cert_verify_sigalg 466{ 467 my $proxy = shift; 468 469 # We're only interested in the CertificateVerify 470 if ($proxy->flight != 1) { 471 return; 472 } 473 474 foreach my $message (@{$proxy->message_list}) { 475 if ($message->mt == TLSProxy::Message::MT_CERTIFICATE_VERIFY) { 476 $message->sigalg(TLSProxy::Message::SIG_ALG_RSA_PSS_PSS_SHA256); 477 $message->repack(); 478 } 479 } 480} 481 482sub inject_unrecognized_sigalg 483{ 484 my $proxy = shift; 485 my $type; 486 487 # We're only interested in the initial ClientHello 488 if ($proxy->flight != 0) { 489 return; 490 } 491 if ($testtype == UNRECOGNIZED_SIGALGS_CERT) { 492 $type = TLSProxy::Message::EXT_SIG_ALGS_CERT; 493 } elsif ($testtype == UNRECOGNIZED_SIGALG) { 494 $type = TLSProxy::Message::EXT_SIG_ALGS; 495 } else { 496 return; 497 } 498 499 my $ext = pack "C8", 500 0x00, 0x06, #Extension length 501 0xfe, 0x18, #private use 502 0x04, 0x01, #rsa_pkcs1_sha256 503 0x08, 0x04; #rsa_pss_rsae_sha256; 504 my $message = ${$proxy->message_list}[0]; 505 $message->set_extension($type, $ext); 506 $message->repack; 507} 508