Commit e0bf61917a for openssl.org

commit e0bf61917aa5e9f75c0ee36d0809620823fe818f
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date:   Sat Jul 18 00:09:30 2026 +0200

    apps: decode DTLSv1.2 records in s_client/s_server -msg output

    The msg_cb message callback used by the -msg option only recognised
    DTLSv1.0 among the DTLS versions, so DTLSv1.2 records were logged as
    "Not TLS data or unknown version" instead of being decoded. Add
    DTLS1_2_VERSION to the recognised version check and to the ssl_versions
    lookup table, and wrap the now-overlong condition.

    Add a test that runs s_client against s_server over TLSv1.2, TLSv1.3 and
    DTLSv1.2, logging the protocol messages via -msg, and checks that every
    record is decoded (no "Not TLS data or unknown version" lines).

    Assisted-by: Claude:claude-opus-4-8

    Reviewed-by: Tim Hudson <tjh@openssl.org>
    Reviewed-by: Paul Dale <paul.dale@oracle.com>
    Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
    MergeDate: Mon Jul 20 09:09:07 2026
    (Merged from https://github.com/openssl/openssl/pull/31994)

diff --git a/apps/lib/s_cb.c b/apps/lib/s_cb.c
index 4f2503502b..f83ffd7236 100644
--- a/apps/lib/s_cb.c
+++ b/apps/lib/s_cb.c
@@ -576,6 +576,7 @@ static STRINT_PAIR ssl_versions[] = {
     { "TLS 1.2", TLS1_2_VERSION },
     { "TLS 1.3", TLS1_3_VERSION },
     { "DTLS 1.0", DTLS1_VERSION },
+    { "DTLS 1.2", DTLS1_2_VERSION },
     { "DTLS 1.0 (bad)", DTLS1_BAD_VER },
     { NULL }
 };
@@ -653,7 +654,10 @@ void msg_cb(int write_p, int version, int content_type, const void *buf,
     const char *str_version, *str_content_type = "", *str_details1 = "", *str_details2 = "";
     const unsigned char *bp = buf;

-    if (version == TLS1_VERSION || version == TLS1_1_VERSION || version == TLS1_2_VERSION || version == TLS1_3_VERSION || version == DTLS1_VERSION || version == DTLS1_BAD_VER) {
+    if (version == TLS1_VERSION || version == TLS1_1_VERSION
+        || version == TLS1_2_VERSION || version == TLS1_3_VERSION
+        || version == DTLS1_VERSION || version == DTLS1_2_VERSION
+        || version == DTLS1_BAD_VER) {
         str_version = lookup(version, ssl_versions, "???");
         switch (content_type) {
         case SSL3_RT_CHANGE_CIPHER_SPEC:
diff --git a/test/recipes/20-test_app_s_client_msg.t b/test/recipes/20-test_app_s_client_msg.t
new file mode 100644
index 0000000000..75d388efe8
--- /dev/null
+++ b/test/recipes/20-test_app_s_client_msg.t
@@ -0,0 +1,110 @@
+#! /usr/bin/env perl
+# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use strict;
+use warnings;
+
+use IPC::Open3;
+use OpenSSL::Test qw/:DEFAULT result_dir srctop_file bldtop_file/;
+use OpenSSL::Test::Utils;
+
+my $test_name = "test_app_s_client_msg";
+setup($test_name);
+
+plan skip_all => "$test_name needs sock enabled"
+    if disabled("sock");
+plan skip_all => "$test_name is not available on Windows or VMS"
+    if $^O =~ /^(VMS|MSWin32|msys)$/;
+
+my $shlib_wrap   = bldtop_file("util", "wrap.pl");
+my $apps_openssl = bldtop_file("apps", "openssl");
+my $server_pem   = srctop_file("test", "certs", "servercert.pem");
+my $server_key   = srctop_file("test", "certs", "serverkey.pem");
+my $resultdir    = result_dir();
+
+# Each case exercises the s_client message callback (-msg) over a different
+# protocol version. Every record must be decoded; before the DTLSv1.2 fix such
+# records were logged as "Not TLS data or unknown version".
+my @cases = (
+    { name => "TLSv1.2",  flag => "-tls1_2",  disabled => "tls1_2" },
+    { name => "TLSv1.3",  flag => "-tls1_3",  disabled => "tls1_3" },
+    { name => "DTLSv1.2", flag => "-dtls1_2", disabled => "dtls1_2" },
+);
+@cases = grep { !disabled($_->{disabled}) } @cases;
+
+plan tests => scalar @cases;
+
+# Run one s_server/s_client handshake logging protocol messages via -msgfile.
+# Returns the number of decoded and undecoded records seen in the log.
+sub run_case
+{
+    my $case = shift;
+    my $msgfile = "$resultdir/s_client-msg-$case->{disabled}.txt";
+    my ($records, $unknown) = (0, 0);
+
+    eval {
+        local $SIG{ALRM} = sub { die "timeout\n" };
+        alarm 60;
+
+        # Start a server speaking just this protocol version
+        my @s_server_cmd = ("s_server", $case->{flag}, "-accept", "0",
+                            "-naccept", "1", "-cert", $server_pem,
+                            "-key", $server_key);
+        my $s_server_pid = open3(my $s_server_i, my $s_server_o, my $s_server_e,
+                                 $shlib_wrap, $apps_openssl, @s_server_cmd);
+
+        # Figure out what port it is listening on
+        my $server_port = "0";
+        while (<$s_server_o>) {
+            print($_);
+            chomp;
+            if (/^ACCEPT \S+?:(\d+)/) {
+                $server_port = $1;
+                last;
+            } elsif (/^Using default/) {
+                ;
+            } else {
+                last;
+            }
+        }
+
+        # Connect a client that logs the protocol messages to a file. -msgfile
+        # sets the log destination but selects SSL_trace; the trailing -msg
+        # switches the callback back to msg_cb (the code under test) while
+        # keeping the file destination.
+        my @s_client_cmd = ("s_client", $case->{flag}, "-msgfile", $msgfile,
+                            "-msg", "-connect", "localhost:$server_port");
+        my $s_client_pid = open3(my $s_client_i, my $s_client_o, my $s_client_e,
+                                 $shlib_wrap, $apps_openssl, @s_client_cmd);
+
+        # Quit the client once connected, then reap both processes
+        print $s_client_i "Q\n";
+        waitpid($s_client_pid, 0);
+        kill 'HUP', $s_server_pid if kill 0, $s_server_pid;
+        waitpid($s_server_pid, 0);
+
+        alarm 0;
+    };
+    die $@ if $@ && $@ ne "timeout\n";
+    print("TIMEOUT: $case->{name} timed out\n") if $@;
+
+    if (open(my $fh, '<', $msgfile)) {
+        while (<$fh>) {
+            $records++ if /^(?:>>>|<<<)/;
+            $unknown++ if /Not TLS data or unknown version/;
+        }
+        close($fh);
+    }
+    return ($records, $unknown);
+}
+
+foreach my $case (@cases) {
+    my ($records, $unknown) = run_case($case);
+    ok($records > 0 && $unknown == 0,
+       "s_client -msg decodes all $case->{name} records");
+}