From 609fe2cae2459d721ac11d23cd27b8a94397ef3c Mon Sep 17 00:00:00 2001 From: "jmc@openbsd.org" Date: Mon, 14 Apr 2025 05:41:42 +0000 Subject: [PATCH 01/68] upstream: rework the text for -3 to make it clearer what default behaviour is, and adjust the text for -R to make them more consistent; issue raised by mikhail mp39590; behaviour explained by naddy ok djm OpenBSD-Commit-ID: 15ff3bd1518d86c84fa8e91d7aa72cfdb41dccc8 --- scp.1 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/scp.1 b/scp.1 index aa2e2d8b53f8..ff739e8b8304 100644 --- a/scp.1 +++ b/scp.1 @@ -8,9 +8,9 @@ .\" .\" Created: Sun May 7 00:14:37 1995 ylo .\" -.\" $OpenBSD: scp.1,v 1.113 2024/12/06 15:12:56 djm Exp $ +.\" $OpenBSD: scp.1,v 1.114 2025/04/14 05:41:42 jmc Exp $ .\" -.Dd $Mdocdate: December 6 2024 $ +.Dd $Mdocdate: April 14 2025 $ .Dt SCP 1 .Os .Sh NAME @@ -76,15 +76,16 @@ The options are as follows: .Bl -tag -width Ds .It Fl 3 Copies between two remote hosts are transferred through the local host. -Without this option the data is copied directly between the two remote -hosts. -Note that, when using the legacy SCP protocol (via the +This mode is the default, +but see also the +.Fl R +option for copying data directly between two remote hosts. +Note that when using the legacy SCP protocol (via the .Fl O flag), this option selects batch mode for the second host as .Nm cannot ask for passwords or passphrases for both hosts. -This mode is the default. .It Fl 4 Forces .Nm @@ -278,7 +279,9 @@ Quiet mode: disables the progress meter as well as warning and diagnostic messages from .Xr ssh 1 . .It Fl R -Copies between two remote hosts are performed by connecting to the origin +Copies between two remote hosts are transferred through the local host +by default. +This option instead copies between two remote hosts by connecting to the origin host and executing .Nm there. From f3d465530e75cb6c02e2cde1d15e6c4bb51ebfd9 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Tue, 15 Apr 2025 04:00:42 +0000 Subject: [PATCH 02/68] upstream: basic benchmarking support for the unit test framework enable with "make UNITTEST_BENCHMARK=yes" ok dtucker@ OpenBSD-Regress-ID: 7f16a2e247f860897ca46ff87bccbe6002a32564 --- Makefile.in | 7 + regress/Makefile | 31 +-- regress/unittests/Makefile.inc | 25 ++- regress/unittests/authopt/Makefile | 4 +- regress/unittests/authopt/tests.c | 8 +- regress/unittests/bitmap/Makefile | 7 +- regress/unittests/bitmap/tests.c | 30 ++- regress/unittests/conversion/Makefile | 4 +- regress/unittests/conversion/tests.c | 8 +- regress/unittests/hostkeys/Makefile | 4 +- regress/unittests/hostkeys/tests.c | 11 +- regress/unittests/kex/Makefile | 4 +- regress/unittests/kex/test_kex.c | 68 +++++-- regress/unittests/kex/tests.c | 9 +- regress/unittests/match/Makefile | 4 +- regress/unittests/match/tests.c | 8 +- regress/unittests/misc/Makefile | 4 +- regress/unittests/misc/tests.c | 8 +- regress/unittests/sshbuf/tests.c | 10 +- regress/unittests/sshkey/test_sshkey.c | 163 ++++++++++++++- regress/unittests/sshkey/tests.c | 10 +- regress/unittests/sshsig/tests.c | 8 +- regress/unittests/test_helper/test_helper.c | 211 +++++++++++++++++--- regress/unittests/test_helper/test_helper.h | 24 ++- regress/unittests/utf8/Makefile | 7 +- regress/unittests/utf8/tests.c | 8 +- 26 files changed, 580 insertions(+), 105 deletions(-) diff --git a/Makefile.in b/Makefile.in index 4617cebcd5e4..5a7a1bd101e3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -782,6 +782,13 @@ unit: regress-unit-binaries OBJ="$(BUILDDIR)/regress" \ $@ && echo $@ tests passed +unit-bench: regress-unit-binaries + cd $(srcdir)/regress || exit $$?; \ + $(MAKE) \ + .CURDIR="$(abs_top_srcdir)/regress" \ + .OBJDIR="$(BUILDDIR)/regress" \ + OBJ="$(BUILDDIR)/regress" $@ + TEST_SSH_SSHD="$(BUILDDIR)/sshd" interop-tests t-exec file-tests extra-tests: regress-prep regress-binaries $(TARGETS) diff --git a/regress/Makefile b/regress/Makefile index 7e7f95b58a2c..8b69e14e998f 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -292,26 +292,33 @@ t-extra: ${EXTRA_TESTS:=.sh} interop: ${INTEROP_TARGETS} # Unit tests, built by top-level Makefile -unit: +unit unit-bench: set -e ; if test -z "${SKIP_UNIT}" ; then \ V="" ; \ test "x${USE_VALGRIND}" = "x" || \ V=${.CURDIR}/valgrind-unit.sh ; \ - $$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf ; \ + ARGS=""; \ + test "x$@" = "xunit-bench" && ARGS="-b"; \ + test "x${UNITTEST_FAST}" = "x" || ARGS="$$ARGS -f"; \ + test "x${UNITTEST_SLOW}" = "x" || ARGS="$$ARGS -F"; \ + test "x${UNITTEST_VERBOSE}" = "x" || ARGS="$$ARGS -v"; \ + test "x${UNITTEST_BENCH_DETAIL}" = "x" || ARGS="$$ARGS -B"; \ + test "x${UNITTEST_BENCH_ONLY}" = "x" || ARGS="$$ARGS -O ${UNITTEST_BENCH_ONLY}"; \ + $$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf $${ARGS}; \ $$V ${.OBJDIR}/unittests/sshkey/test_sshkey \ - -d ${.CURDIR}/unittests/sshkey/testdata ; \ + -d ${.CURDIR}/unittests/sshkey/testdata $${ARGS}; \ $$V ${.OBJDIR}/unittests/sshsig/test_sshsig \ - -d ${.CURDIR}/unittests/sshsig/testdata ; \ + -d ${.CURDIR}/unittests/sshsig/testdata $${ARGS}; \ $$V ${.OBJDIR}/unittests/authopt/test_authopt \ - -d ${.CURDIR}/unittests/authopt/testdata ; \ - $$V ${.OBJDIR}/unittests/bitmap/test_bitmap ; \ - $$V ${.OBJDIR}/unittests/conversion/test_conversion ; \ - $$V ${.OBJDIR}/unittests/kex/test_kex ; \ + -d ${.CURDIR}/unittests/authopt/testdata $${ARGS}; \ + $$V ${.OBJDIR}/unittests/bitmap/test_bitmap $${ARGS}; \ + $$V ${.OBJDIR}/unittests/conversion/test_conversion $${ARGS}; \ + $$V ${.OBJDIR}/unittests/kex/test_kex $${ARGS}; \ $$V ${.OBJDIR}/unittests/hostkeys/test_hostkeys \ - -d ${.CURDIR}/unittests/hostkeys/testdata ; \ - $$V ${.OBJDIR}/unittests/match/test_match ; \ - $$V ${.OBJDIR}/unittests/misc/test_misc ; \ + -d ${.CURDIR}/unittests/hostkeys/testdata $${ARGS}; \ + $$V ${.OBJDIR}/unittests/match/test_match $${ARGS}; \ + $$V ${.OBJDIR}/unittests/misc/test_misc $${ARGS}; \ if test "x${TEST_SSH_UTF8}" = "xyes" ; then \ - $$V ${.OBJDIR}/unittests/utf8/test_utf8 ; \ + $$V ${.OBJDIR}/unittests/utf8/test_utf8 $${ARGS}; \ fi \ fi diff --git a/regress/unittests/Makefile.inc b/regress/unittests/Makefile.inc index 98e280486ab1..ad7fdad84a53 100644 --- a/regress/unittests/Makefile.inc +++ b/regress/unittests/Makefile.inc @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile.inc,v 1.16 2024/01/11 01:45:58 djm Exp $ +# $OpenBSD: Makefile.inc,v 1.17 2025/04/15 04:00:42 djm Exp $ .include .include @@ -7,6 +7,9 @@ UNITTEST_FAST?= no # Skip slow tests (e.g. less intensive fuzzing). UNITTEST_SLOW?= no # Include slower tests (e.g. more intensive fuzzing). UNITTEST_VERBOSE?= no # Verbose test output (inc. per-test names). +UNITTEST_BENCHMARK?= no # Run unit tests in benchmarking mode. +UNITTEST_BENCH_DETAIL?=no # Detailed benchmark statistics. +UNITTEST_BENCH_ONLY?= # Run only these benchmarks MALLOC_OPTIONS?= CFGJRSUX TEST_ENV?= MALLOC_OPTIONS=${MALLOC_OPTIONS} @@ -69,8 +72,8 @@ DPADD+=${.CURDIR}/../test_helper/libtest_helper.a .PATH: ${.CURDIR}/${SSHREL} -LDADD+= -lutil -DPADD+= ${LIBUTIL} +LDADD+= -lutil -lm +DPADD+= ${LIBUTIL} ${LIBM} .if (${OPENSSL:L} == "yes") LDADD+= -lcrypto @@ -82,11 +85,21 @@ DPADD+= ${LIBFIDO2} ${LIBCBOR} ${LIBUSBHID} UNITTEST_ARGS?= -.if (${UNITTEST_VERBOSE:L} != "no") +.if (${UNITTEST_VERBOSE:L:R} != "no") UNITTEST_ARGS+= -v .endif -.if (${UNITTEST_FAST:L} != "no") +.if (${UNITTEST_FAST:L:R} != "no") UNITTEST_ARGS+= -f -.elif (${UNITTEST_SLOW:L} != "no") +.elif (${UNITTEST_SLOW:L:R} != "no") UNITTEST_ARGS+= -F .endif + +.if (${UNITTEST_BENCHMARK:L:R} != "no") +UNITTEST_ARGS+= -b +.endif +.if (${UNITTEST_BENCH_DETAIL:L:R} != "no") +UNITTEST_ARGS+= -B +.endif +.if (${UNITTEST_BENCH_ONLY:L} != "") +UNITTEST_ARGS+= -O "${UNITTEST_BENCH_ONLY}" +.endif diff --git a/regress/unittests/authopt/Makefile b/regress/unittests/authopt/Makefile index 3045ec708165..8bed7a915dfa 100644 --- a/regress/unittests/authopt/Makefile +++ b/regress/unittests/authopt/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.7 2023/01/15 23:35:10 djm Exp $ +# $OpenBSD: Makefile,v 1.8 2025/04/15 04:00:42 djm Exp $ PROG=test_authopt SRCS=tests.c @@ -22,6 +22,6 @@ SRCS+=utf8.c REGRESS_TARGETS=run-regress-${PROG} run-regress-${PROG}: ${PROG} - env ${TEST_ENV} ./${PROG} -d ${.CURDIR}/testdata + env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} -d ${.CURDIR}/testdata .include diff --git a/regress/unittests/authopt/tests.c b/regress/unittests/authopt/tests.c index d9e190305e76..5285f0db5746 100644 --- a/regress/unittests/authopt/tests.c +++ b/regress/unittests/authopt/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.3 2021/12/14 21:25:27 deraadt Exp $ */ +/* $OpenBSD: tests.c,v 1.4 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for keys options functions. @@ -576,3 +576,9 @@ tests(void) test_cert_parse(); test_merge(); } + +void +benchmarks(void) +{ + printf("no benchmarks\n"); +} diff --git a/regress/unittests/bitmap/Makefile b/regress/unittests/bitmap/Makefile index fe30acc77394..c38cc7918cc1 100644 --- a/regress/unittests/bitmap/Makefile +++ b/regress/unittests/bitmap/Makefile @@ -1,14 +1,15 @@ -# $OpenBSD: Makefile,v 1.4 2017/12/21 00:41:22 djm Exp $ +# $OpenBSD: Makefile,v 1.5 2025/04/15 04:00:42 djm Exp $ PROG=test_bitmap SRCS=tests.c # From usr.sbin/ssh -SRCS+=bitmap.c atomicio.c +SRCS+=bitmap.c atomicio.c misc.c xmalloc.c fatal.c log.c cleanup.c match.c +SRCS+=sshbuf.c sshbuf-getput-basic.c sshbuf-misc.c ssherr.c addr.c addrmatch.c REGRESS_TARGETS=run-regress-${PROG} run-regress-${PROG}: ${PROG} - env ${TEST_ENV} ./${PROG} + env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} .include diff --git a/regress/unittests/bitmap/tests.c b/regress/unittests/bitmap/tests.c index 576b863f4066..b8eae2313215 100644 --- a/regress/unittests/bitmap/tests.c +++ b/regress/unittests/bitmap/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.2 2021/12/14 21:25:27 deraadt Exp $ */ +/* $OpenBSD: tests.c,v 1.3 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for bitmap.h bitmap API * @@ -23,7 +23,7 @@ #include "bitmap.h" -#define NTESTS 131 +#define DEFAULT_NTESTS 131 void tests(void) @@ -32,10 +32,15 @@ tests(void) struct bitmap *b; BIGNUM *bn; size_t len; - int i, j, k, n; + int i, j, k, n, ntests = DEFAULT_NTESTS; u_char bbuf[1024], bnbuf[1024]; int r; + if (test_is_fast()) + ntests /= 4; + else if (test_is_slow()) + ntests *= 2; + TEST_START("bitmap_new"); b = bitmap_new(); ASSERT_PTR_NE(b, NULL); @@ -44,9 +49,9 @@ tests(void) TEST_DONE(); TEST_START("bitmap_set_bit / bitmap_test_bit"); - for (i = -1; i < NTESTS; i++) { - for (j = -1; j < NTESTS; j++) { - for (k = -1; k < NTESTS; k++) { + for (i = -1; i < ntests; i++) { + for (j = -1; j < ntests; j++) { + for (k = -1; k < ntests; k++) { bitmap_zero(b); BN_clear(bn); @@ -67,7 +72,7 @@ tests(void) /* Check perfect match between bitmap and bn */ test_subtest_info("match %d/%d/%d", i, j, k); - for (n = 0; n < NTESTS; n++) { + for (n = 0; n < ntests; n++) { ASSERT_INT_EQ(BN_is_bit_set(bn, n), bitmap_test_bit(b, n)); } @@ -99,7 +104,7 @@ tests(void) bitmap_zero(b); ASSERT_INT_EQ(bitmap_from_string(b, bnbuf, len), 0); - for (n = 0; n < NTESTS; n++) { + for (n = 0; n < ntests; n++) { ASSERT_INT_EQ(BN_is_bit_set(bn, n), bitmap_test_bit(b, n)); } @@ -107,7 +112,7 @@ tests(void) /* Test clearing bits */ test_subtest_info("clear %d/%d/%d", i, j, k); - for (n = 0; n < NTESTS; n++) { + for (n = 0; n < ntests; n++) { ASSERT_INT_EQ(bitmap_set_bit(b, n), 0); ASSERT_INT_EQ(BN_set_bit(bn, n), 1); } @@ -123,7 +128,7 @@ tests(void) bitmap_clear_bit(b, k); BN_clear_bit(bn, k); } - for (n = 0; n < NTESTS; n++) { + for (n = 0; n < ntests; n++) { ASSERT_INT_EQ(BN_is_bit_set(bn, n), bitmap_test_bit(b, n)); } @@ -135,4 +140,9 @@ tests(void) TEST_DONE(); #endif } +void +benchmarks(void) +{ + printf("no benchmarks\n"); +} diff --git a/regress/unittests/conversion/Makefile b/regress/unittests/conversion/Makefile index 5793c4934845..f9f5859ac5e8 100644 --- a/regress/unittests/conversion/Makefile +++ b/regress/unittests/conversion/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.4 2021/01/09 12:24:30 dtucker Exp $ +# $OpenBSD: Makefile,v 1.5 2025/04/15 04:00:42 djm Exp $ PROG=test_conversion SRCS=tests.c @@ -11,6 +11,6 @@ SRCS+=match.c addr.c addrmatch.c REGRESS_TARGETS=run-regress-${PROG} run-regress-${PROG}: ${PROG} - env ${TEST_ENV} ./${PROG} + env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} .include diff --git a/regress/unittests/conversion/tests.c b/regress/unittests/conversion/tests.c index 5b526f7afa07..d65e6326fd4e 100644 --- a/regress/unittests/conversion/tests.c +++ b/regress/unittests/conversion/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.4 2021/12/14 21:25:27 deraadt Exp $ */ +/* $OpenBSD: tests.c,v 1.5 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for conversions * @@ -50,3 +50,9 @@ tests(void) ASSERT_INT_EQ(convtime("1000000000000000000000w"), -1); TEST_DONE(); } + +void +benchmarks(void) +{ + printf("no benchmarks\n"); +} diff --git a/regress/unittests/hostkeys/Makefile b/regress/unittests/hostkeys/Makefile index 04d93359acaa..79a9d5745419 100644 --- a/regress/unittests/hostkeys/Makefile +++ b/regress/unittests/hostkeys/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.10 2023/01/15 23:35:10 djm Exp $ +# $OpenBSD: Makefile,v 1.11 2025/04/15 04:00:42 djm Exp $ PROG=test_hostkeys SRCS=tests.c test_iterate.c @@ -20,6 +20,6 @@ SRCS+=utf8.c REGRESS_TARGETS=run-regress-${PROG} run-regress-${PROG}: ${PROG} - env ${TEST_ENV} ./${PROG} -d ${.CURDIR}/testdata + env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} -d ${.CURDIR}/testdata .include diff --git a/regress/unittests/hostkeys/tests.c b/regress/unittests/hostkeys/tests.c index 92c7646ad164..a14ba19b3abe 100644 --- a/regress/unittests/hostkeys/tests.c +++ b/regress/unittests/hostkeys/tests.c @@ -1,10 +1,14 @@ -/* $OpenBSD: tests.c,v 1.1 2015/02/16 22:18:34 djm Exp $ */ +/* $OpenBSD: tests.c,v 1.2 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for known_hosts-related API. * * Placed in the public domain */ +#include + +#include "../test_helper/test_helper.h" + void tests(void); void test_iterate(void); /* test_iterate.c */ @@ -14,3 +18,8 @@ tests(void) test_iterate(); } +void +benchmarks(void) +{ + printf("no benchmarks\n"); +} diff --git a/regress/unittests/kex/Makefile b/regress/unittests/kex/Makefile index ca4f0ee38639..b76ee8edc813 100644 --- a/regress/unittests/kex/Makefile +++ b/regress/unittests/kex/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.16 2024/09/09 03:13:39 djm Exp $ +# $OpenBSD: Makefile,v 1.17 2025/04/15 04:00:42 djm Exp $ PROG=test_kex SRCS=tests.c test_kex.c test_proposal.c @@ -35,7 +35,7 @@ SRCS+=digest-openssl.c REGRESS_TARGETS=run-regress-${PROG} run-regress-${PROG}: ${PROG} - env ${TEST_ENV} ./${PROG} + env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} .include diff --git a/regress/unittests/kex/test_kex.c b/regress/unittests/kex/test_kex.c index caf8f57f75d6..84dada301b8f 100644 --- a/regress/unittests/kex/test_kex.c +++ b/regress/unittests/kex/test_kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_kex.c,v 1.9 2024/09/09 03:13:39 djm Exp $ */ +/* $OpenBSD: test_kex.c,v 1.10 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test KEX * @@ -8,6 +8,7 @@ #include "includes.h" #include +#include #include #ifdef HAVE_STDINT_H #include @@ -76,7 +77,8 @@ run_kex(struct ssh *client, struct ssh *server) } static void -do_kex_with_key(char *kex, int keytype, int bits) +do_kex_with_key(char *kex, char *cipher, char *mac, + struct sshkey *key, int keytype, int bits) { struct ssh *client = NULL, *server = NULL, *server2 = NULL; struct sshkey *private, *public; @@ -85,9 +87,14 @@ do_kex_with_key(char *kex, int keytype, int bits) char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; char *keyname = NULL; - TEST_START("sshkey_generate"); - ASSERT_INT_EQ(sshkey_generate(keytype, bits, &private), 0); - TEST_DONE(); + if (key != NULL) { + private = key; + keytype = key->type; + } else { + TEST_START("sshkey_generate"); + ASSERT_INT_EQ(sshkey_generate(keytype, bits, &private), 0); + TEST_DONE(); + } TEST_START("sshkey_from_private"); ASSERT_INT_EQ(sshkey_from_private(private, &public), 0); @@ -97,6 +104,14 @@ do_kex_with_key(char *kex, int keytype, int bits) memcpy(kex_params.proposal, myproposal, sizeof(myproposal)); if (kex != NULL) kex_params.proposal[PROPOSAL_KEX_ALGS] = kex; + if (cipher != NULL) { + kex_params.proposal[PROPOSAL_ENC_ALGS_CTOS] = cipher; + kex_params.proposal[PROPOSAL_ENC_ALGS_STOC] = cipher; + } + if (mac != NULL) { + kex_params.proposal[PROPOSAL_MAC_ALGS_CTOS] = mac; + kex_params.proposal[PROPOSAL_MAC_ALGS_STOC] = mac; + } keyname = strdup(sshkey_ssh_name(private)); ASSERT_PTR_NE(keyname, NULL); kex_params.proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = keyname; @@ -167,7 +182,8 @@ do_kex_with_key(char *kex, int keytype, int bits) TEST_DONE(); TEST_START("cleanup"); - sshkey_free(private); + if (key == NULL) + sshkey_free(private); sshkey_free(public); ssh_free(client); ssh_free(server); @@ -179,19 +195,37 @@ do_kex_with_key(char *kex, int keytype, int bits) static void do_kex(char *kex) { -#if 0 - log_init("test_kex", SYSLOG_LEVEL_DEBUG3, SYSLOG_FACILITY_AUTH, 1); -#endif + struct sshkey *key = NULL; + char name[256]; + + if (test_is_benchmark()) { + snprintf(name, sizeof(name), "generate %s", kex); + TEST_START(name); + ASSERT_INT_EQ(sshkey_generate(KEY_ED25519, 0, &key), 0); + TEST_DONE(); + snprintf(name, sizeof(name), "KEX %s", kex); + BENCH_START(name); + /* + * NB. use a cipher/MAC here that requires minimal bits from + * the KEX to avoid DH-GEX taking forever. + */ + do_kex_with_key(kex, "aes128-ctr", "hmac-sha2-256", key, + KEY_ED25519, 256); + BENCH_FINISH("kex"); + sshkey_free(key); + return; + } + #ifdef WITH_OPENSSL - do_kex_with_key(kex, KEY_RSA, 2048); -#ifdef WITH_DSA - do_kex_with_key(kex, KEY_DSA, 1024); -#endif -#ifdef OPENSSL_HAS_ECC - do_kex_with_key(kex, KEY_ECDSA, 256); -#endif /* OPENSSL_HAS_ECC */ + do_kex_with_key(kex, NULL, NULL, NULL, KEY_RSA, 2048); +# ifdef WITH_DSA + do_kex_with_key(kex, NULL, NULL, NULL, KEY_DSA, 1024); +# endif /* WITH_DSA */ +# ifdef OPENSSL_HAS_ECC + do_kex_with_key(kex, NULL, NULL, NULL, KEY_ECDSA, 256); +# endif /* OPENSSL_HAS_ECC */ #endif /* WITH_OPENSSL */ - do_kex_with_key(kex, KEY_ED25519, 256); + do_kex_with_key(kex, NULL, NULL, NULL, KEY_ED25519, 256); } void diff --git a/regress/unittests/kex/tests.c b/regress/unittests/kex/tests.c index d3044f033767..a3ef19ef410a 100644 --- a/regress/unittests/kex/tests.c +++ b/regress/unittests/kex/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.3 2023/03/06 12:15:47 dtucker Exp $ */ +/* $OpenBSD: tests.c,v 1.4 2025/04/15 04:00:42 djm Exp $ */ /* * Placed in the public domain */ @@ -16,3 +16,10 @@ tests(void) kex_proposal_tests(); kex_proposal_populate_tests(); } + +void +benchmarks(void) +{ + printf("\n"); + kex_tests(); +} diff --git a/regress/unittests/match/Makefile b/regress/unittests/match/Makefile index 939163d30ef5..7b17e5689344 100644 --- a/regress/unittests/match/Makefile +++ b/regress/unittests/match/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.5 2021/01/09 12:24:31 dtucker Exp $ +# $OpenBSD: Makefile,v 1.6 2025/04/15 04:00:42 djm Exp $ PROG=test_match SRCS=tests.c @@ -11,6 +11,6 @@ SRCS+=cleanup.c atomicio.c addr.c REGRESS_TARGETS=run-regress-${PROG} run-regress-${PROG}: ${PROG} - env ${TEST_ENV} ./${PROG} + env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} .include diff --git a/regress/unittests/match/tests.c b/regress/unittests/match/tests.c index f00d1f9348fc..2ca6c769ae3f 100644 --- a/regress/unittests/match/tests.c +++ b/regress/unittests/match/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.8 2021/12/14 21:25:27 deraadt Exp $ */ +/* $OpenBSD: tests.c,v 1.9 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for matching functions * @@ -129,3 +129,9 @@ tests(void) * int addr_match_cidr_list(const char *, const char *); */ } + +void +benchmarks(void) +{ + printf("no benchmarks\n"); +} diff --git a/regress/unittests/misc/Makefile b/regress/unittests/misc/Makefile index d2be393ad703..7282be13a667 100644 --- a/regress/unittests/misc/Makefile +++ b/regress/unittests/misc/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.9 2023/01/06 02:59:50 djm Exp $ +# $OpenBSD: Makefile,v 1.10 2025/04/15 04:00:42 djm Exp $ PROG=test_misc SRCS=tests.c @@ -28,6 +28,6 @@ SRCS+= atomicio.c cleanup.c fatal.c REGRESS_TARGETS=run-regress-${PROG} run-regress-${PROG}: ${PROG} - env ${TEST_ENV} ./${PROG} + env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} .include diff --git a/regress/unittests/misc/tests.c b/regress/unittests/misc/tests.c index 32699541413e..7611a0d3b645 100644 --- a/regress/unittests/misc/tests.c +++ b/regress/unittests/misc/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.10 2023/01/06 02:59:50 djm Exp $ */ +/* $OpenBSD: tests.c,v 1.11 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for misc helper functions. * @@ -39,3 +39,9 @@ tests(void) test_hpdelim(); test_ptimeout(); } + +void +benchmarks(void) +{ + printf("no benchmarks\n"); +} diff --git a/regress/unittests/sshbuf/tests.c b/regress/unittests/sshbuf/tests.c index 29916a10bc5b..eb801fb3b302 100644 --- a/regress/unittests/sshbuf/tests.c +++ b/regress/unittests/sshbuf/tests.c @@ -1,10 +1,12 @@ -/* $OpenBSD: tests.c,v 1.1 2014/04/30 05:32:00 djm Exp $ */ +/* $OpenBSD: tests.c,v 1.2 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for sshbuf.h buffer API * * Placed in the public domain */ +#include + #include "../test_helper/test_helper.h" void sshbuf_tests(void); @@ -28,3 +30,9 @@ tests(void) sshbuf_getput_fuzz_tests(); sshbuf_fixed(); } + +void +benchmarks(void) +{ + printf("no benchmarks\n"); +} diff --git a/regress/unittests/sshkey/test_sshkey.c b/regress/unittests/sshkey/test_sshkey.c index 5bf4b65cc055..fc744eb7430f 100644 --- a/regress/unittests/sshkey/test_sshkey.c +++ b/regress/unittests/sshkey/test_sshkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_sshkey.c,v 1.25 2024/08/15 00:52:23 djm Exp $ */ +/* $OpenBSD: test_sshkey.c,v 1.26 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for sshkey.h key management API * @@ -36,6 +36,7 @@ #include "ssh2.h" void sshkey_tests(void); +void sshkey_benchmarks(void); static void put_opt(struct sshbuf *b, const char *name, const char *value) @@ -133,6 +134,55 @@ signature_test(struct sshkey *k, struct sshkey *bad, const char *sig_alg, free(sig); } +static void +signature_bench(const char *name, int ktype, int bits, const char *sig_alg, + const u_char *d, size_t l) +{ + struct sshkey *k; + size_t len; + u_char *sig; + char testname[256]; + + snprintf(testname, sizeof(testname), "sign %s", name); + TEST_START(testname); + ASSERT_INT_EQ(sshkey_generate(ktype, bits, &k), 0); + ASSERT_PTR_NE(k, NULL); + + BENCH_START(testname); + ASSERT_INT_EQ(sshkey_sign(k, &sig, &len, d, l, sig_alg, + NULL, NULL, 0), 0); + free(sig); + BENCH_FINISH("sign"); + + sshkey_free(k); + TEST_DONE(); +} + +static void +verify_bench(const char *name, int ktype, int bits, const char *sig_alg, + const u_char *d, size_t l) +{ + struct sshkey *k; + size_t len; + u_char *sig; + char testname[256]; + + snprintf(testname, sizeof(testname), "verify %s", name); + TEST_START(testname); + ASSERT_INT_EQ(sshkey_generate(ktype, bits, &k), 0); + ASSERT_PTR_NE(k, NULL); + + ASSERT_INT_EQ(sshkey_sign(k, &sig, &len, d, l, sig_alg, + NULL, NULL, 0), 0); + BENCH_START(testname); + ASSERT_INT_EQ(sshkey_verify(k, sig, len, d, l, NULL, 0, NULL), 0); + BENCH_FINISH("verify"); + + free(sig); + sshkey_free(k); + TEST_DONE(); +} + static void banana(u_char *s, size_t l) { @@ -165,6 +215,19 @@ signature_tests(struct sshkey *k, struct sshkey *bad, const char *sig_alg) } } +static void +signature_benchmark(const char *name, int ktype, int bits, + const char *sig_alg, int bench_verify) +{ + u_char buf[256]; + + banana(buf, sizeof(buf)); + if (bench_verify) + verify_bench(name, ktype, bits, sig_alg, buf, sizeof(buf)); + else + signature_bench(name, ktype, bits, sig_alg, buf, sizeof(buf)); +} + static struct sshkey * get_private(const char *n) { @@ -537,3 +600,101 @@ sshkey_tests(void) TEST_DONE(); #endif /* WITH_OPENSSL */ } + +void +sshkey_benchmarks(void) +{ + struct sshkey *k = NULL; + +#ifdef WITH_OPENSSL + BENCH_START("generate RSA-1024"); + TEST_START("generate KEY_RSA"); + ASSERT_INT_EQ(sshkey_generate(KEY_RSA, 1024, &k), 0); + ASSERT_PTR_NE(k, NULL); + sshkey_free(k); + TEST_DONE(); + BENCH_FINISH("keys"); + + BENCH_START("generate RSA-2048"); + TEST_START("generate KEY_RSA"); + ASSERT_INT_EQ(sshkey_generate(KEY_RSA, 2048, &k), 0); + ASSERT_PTR_NE(k, NULL); + sshkey_free(k); + TEST_DONE(); + BENCH_FINISH("keys"); + +#ifdef WITH_DSA + BENCH_START("generate DSA-1024"); + TEST_START("generate KEY_DSA"); + ASSERT_INT_EQ(sshkey_generate(KEY_DSA, 1024, &k), 0); + ASSERT_PTR_NE(k, NULL); + sshkey_free(k); + TEST_DONE(); + BENCH_FINISH("keys"); +#endif + + BENCH_START("generate ECDSA-256"); + TEST_START("generate KEY_ECDSA"); + ASSERT_INT_EQ(sshkey_generate(KEY_ECDSA, 256, &k), 0); + ASSERT_PTR_NE(k, NULL); + sshkey_free(k); + TEST_DONE(); + BENCH_FINISH("keys"); + + BENCH_START("generate ECDSA-384"); + TEST_START("generate KEY_ECDSA"); + ASSERT_INT_EQ(sshkey_generate(KEY_ECDSA, 384, &k), 0); + ASSERT_PTR_NE(k, NULL); + sshkey_free(k); + TEST_DONE(); + BENCH_FINISH("keys"); + + BENCH_START("generate ECDSA-521"); + TEST_START("generate KEY_ECDSA"); + ASSERT_INT_EQ(sshkey_generate(KEY_ECDSA, 521, &k), 0); + ASSERT_PTR_NE(k, NULL); + sshkey_free(k); + TEST_DONE(); + BENCH_FINISH("keys"); +#endif /* WITH_OPENSSL */ + + BENCH_START("generate ED25519"); + TEST_START("generate KEY_ED25519"); + ASSERT_INT_EQ(sshkey_generate(KEY_ED25519, 256, &k), 0); + ASSERT_PTR_NE(k, NULL); + sshkey_free(k); + TEST_DONE(); + BENCH_FINISH("keys"); + +#ifdef WITH_OPENSSL + /* sign */ + signature_benchmark("RSA-1024/SHA1", KEY_RSA, 1024, "ssh-rsa", 0); + signature_benchmark("RSA-1024/SHA256", KEY_RSA, 1024, "rsa-sha2-256", 0); + signature_benchmark("RSA-1024/SHA512", KEY_RSA, 1024, "rsa-sha2-512", 0); + signature_benchmark("RSA-2048/SHA1", KEY_RSA, 2048, "ssh-rsa", 0); + signature_benchmark("RSA-2048/SHA256", KEY_RSA, 2048, "rsa-sha2-256", 0); + signature_benchmark("RSA-2048/SHA512", KEY_RSA, 2048, "rsa-sha2-512", 0); +#ifdef WITH_DSA + signature_benchmark("DSA-1024", KEY_DSA, 1024, NULL, 0); +#endif + signature_benchmark("ECDSA-256", KEY_ECDSA, 256, NULL, 0); + signature_benchmark("ECDSA-384", KEY_ECDSA, 384, NULL, 0); + signature_benchmark("ECDSA-521", KEY_ECDSA, 521, NULL, 0); + signature_benchmark("ED25519", KEY_ED25519, 0, NULL, 0); + + /* verify */ + signature_benchmark("RSA-1024/SHA1", KEY_RSA, 1024, "ssh-rsa", 1); + signature_benchmark("RSA-1024/SHA256", KEY_RSA, 1024, "rsa-sha2-256", 1); + signature_benchmark("RSA-1024/SHA512", KEY_RSA, 1024, "rsa-sha2-512", 1); + signature_benchmark("RSA-2048/SHA1", KEY_RSA, 2048, "ssh-rsa", 1); + signature_benchmark("RSA-2048/SHA256", KEY_RSA, 2048, "rsa-sha2-256", 1); + signature_benchmark("RSA-2048/SHA512", KEY_RSA, 2048, "rsa-sha2-512", 1); +#ifdef WITH_DSA + signature_benchmark("DSA-1024", KEY_DSA, 1024, NULL, 1); +#endif + signature_benchmark("ECDSA-256", KEY_ECDSA, 256, NULL, 1); + signature_benchmark("ECDSA-384", KEY_ECDSA, 384, NULL, 1); + signature_benchmark("ECDSA-521", KEY_ECDSA, 521, NULL, 1); +#endif /* WITH_OPENSSL */ + signature_benchmark("ED25519", KEY_ED25519, 0, NULL, 1); +} diff --git a/regress/unittests/sshkey/tests.c b/regress/unittests/sshkey/tests.c index 78aa9223d42b..5511e7b8900d 100644 --- a/regress/unittests/sshkey/tests.c +++ b/regress/unittests/sshkey/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.1 2014/06/24 01:14:18 djm Exp $ */ +/* $OpenBSD: tests.c,v 1.2 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for sshbuf.h buffer API * @@ -12,6 +12,7 @@ void sshkey_tests(void); void sshkey_file_tests(void); void sshkey_fuzz_tests(void); +void sshkey_benchmarks(void); void tests(void) @@ -20,3 +21,10 @@ tests(void) sshkey_file_tests(); sshkey_fuzz_tests(); } + +void +benchmarks(void) +{ + printf("\n"); + sshkey_benchmarks(); +} diff --git a/regress/unittests/sshsig/tests.c b/regress/unittests/sshsig/tests.c index 80966bdd2c27..7fcf9488d270 100644 --- a/regress/unittests/sshsig/tests.c +++ b/regress/unittests/sshsig/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.4 2024/01/11 01:45:59 djm Exp $ */ +/* $OpenBSD: tests.c,v 1.5 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for sshbuf.h buffer API * @@ -142,3 +142,9 @@ tests(void) sshbuf_free(msg); free(namespace); } + +void +benchmarks(void) +{ + printf("no benchmarks\n"); +} diff --git a/regress/unittests/test_helper/test_helper.c b/regress/unittests/test_helper/test_helper.c index e23128aa5599..b75f7701401b 100644 --- a/regress/unittests/test_helper/test_helper.c +++ b/regress/unittests/test_helper/test_helper.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_helper.c,v 1.13 2021/12/14 21:25:27 deraadt Exp $ */ +/* $OpenBSD: test_helper.c,v 1.14 2025/04/15 04:00:42 djm Exp $ */ /* * Copyright (c) 2011 Damien Miller * @@ -21,18 +21,21 @@ #include #include - -#include +#include + +#include #include -#include +#include +#include +#include +#include #ifdef HAVE_STDINT_H # include #endif +#include #include #include -#include #include -#include #ifdef WITH_OPENSSL #include @@ -43,12 +46,21 @@ # include #endif -#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) - #include "entropy.h" #include "test_helper.h" #include "atomicio.h" +#include "match.h" +#include "misc.h" +#include "xmalloc.h" +#define BENCH_FAST_DEADLINE 1 +#define BENCH_NORMAL_DEADLINE 10 +#define BENCH_SLOW_DEADLINE 60 +#define BENCH_SAMPLES_ALLOC 8192 +#define BENCH_COLUMN_WIDTH 40 + +#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) + #define TEST_CHECK_INT(r, pred) do { \ switch (pred) { \ case TEST_EQ: \ @@ -123,6 +135,15 @@ static const char *data_dir = NULL; static char subtest_info[512]; static int fast = 0; static int slow = 0; +static int benchmark_detail_statistics = 0; + +static int benchmark = 0; +static const char *bench_name = NULL; +static char *benchmark_pattern = NULL; +static struct timespec bench_start_time, bench_finish_time; +static struct timespec *bench_samples; +static int bench_skip, bench_nruns, bench_nalloc; +double bench_accum_secs; int main(int argc, char **argv) @@ -147,8 +168,17 @@ main(int argc, char **argv) } } - while ((ch = getopt(argc, argv, "Ffvqd:")) != -1) { + while ((ch = getopt(argc, argv, "O:bBFfvqd:")) != -1) { switch (ch) { + case 'b': + benchmark = 1; + break; + case 'B': + benchmark = benchmark_detail_statistics = 1; + break; + case 'O': + benchmark_pattern = xstrdup(optarg); + break; case 'F': slow = 1; break; @@ -168,7 +198,8 @@ main(int argc, char **argv) break; default: fprintf(stderr, "Unrecognised command line option\n"); - fprintf(stderr, "Usage: %s [-v]\n", __progname); + fprintf(stderr, "Usage: %s [-vqfFbB] [-d data_dir] " + "[-O pattern]\n", __progname); exit(1); } } @@ -178,9 +209,12 @@ main(int argc, char **argv) if (verbose_mode) printf("\n"); - tests(); + if (benchmark) + benchmarks(); + else + tests(); - if (!quiet_mode) + if (!quiet_mode && !benchmark) printf(" %u tests ok\n", test_number); return 0; } @@ -274,7 +308,7 @@ test_done(void) active_test_name = NULL; if (verbose_mode) printf("OK\n"); - else if (!quiet_mode) { + else if (!quiet_mode && !benchmark) { printf("."); fflush(stdout); } @@ -290,6 +324,12 @@ test_subtest_info(const char *fmt, ...) va_end(ap); } +int +test_is_benchmark(void) +{ + return benchmark; +} + void ssl_err_check(const char *file, int line) { @@ -382,23 +422,6 @@ assert_string(const char *file, int line, const char *a1, const char *a2, test_die(); } -static char * -tohex(const void *_s, size_t l) -{ - u_int8_t *s = (u_int8_t *)_s; - size_t i, j; - const char *hex = "0123456789abcdef"; - char *r = malloc((l * 2) + 1); - - assert(r != NULL); - for (i = j = 0; i < l; i++) { - r[j++] = hex[(s[i] >> 4) & 0xf]; - r[j++] = hex[s[i] & 0xf]; - } - r[j] = '\0'; - return r; -} - void assert_mem(const char *file, int line, const char *a1, const char *a2, const void *aa1, const void *aa2, size_t l, enum test_predicate pred) @@ -593,3 +616,131 @@ assert_ptr(const char *file, int line, const char *a1, const char *a2, test_die(); } +static double +tstod(const struct timespec *ts) +{ + return (double)ts->tv_sec + ((double)ts->tv_nsec / 1000000000.0); +} + +void +bench_start(const char *file, int line, const char *name) +{ + char *cp; + + if (bench_name != NULL) { + fprintf(stderr, "\n%s:%d internal error: BENCH_START() called " + "while previous benchmark \"%s\" incomplete", + file, line, bench_name); + abort(); + } + cp = xstrdup(name); + lowercase(cp); + bench_skip = benchmark_pattern != NULL && + match_pattern_list(cp, benchmark_pattern, 1) != 1; + free(cp); + + bench_name = name; + bench_nruns = 0; + if (bench_skip) + return; + free(bench_samples); + bench_nalloc = BENCH_SAMPLES_ALLOC; + bench_samples = xcalloc(sizeof(*bench_samples), bench_nalloc); + bench_accum_secs = 0; +} + +int +bench_done(void) +{ + return bench_skip || bench_accum_secs >= (fast ? BENCH_FAST_DEADLINE : + (slow ? BENCH_SLOW_DEADLINE : BENCH_NORMAL_DEADLINE)); +} + +void +bench_case_start(const char *file, int line) +{ + clock_gettime(CLOCK_REALTIME, &bench_start_time); +} + +void +bench_case_finish(const char *file, int line) +{ + struct timespec ts; + + clock_gettime(CLOCK_REALTIME, &bench_finish_time); + timespecsub(&bench_finish_time, &bench_start_time, &ts); + if (bench_nruns >= bench_nalloc) { + if (bench_nalloc >= INT_MAX / 2) { + fprintf(stderr, "\n%s:%d benchmark %s too many samples", + __FILE__, __LINE__, bench_name); + abort(); + } + bench_samples = xrecallocarray(bench_samples, bench_nalloc, + bench_nalloc * 2, sizeof(*bench_samples)); + bench_nalloc *= 2; + } + bench_samples[bench_nruns++] = ts; + bench_accum_secs += tstod(&ts); +} + +static int +tscmp(const void *aa, const void *bb) +{ + const struct timespec *a = (const struct timespec *)aa; + const struct timespec *b = (const struct timespec *)bb; + + if (timespeccmp(a, b, ==)) + return 0; + return timespeccmp(a, b, <) ? -1 : 1; +} + +void +bench_finish(const char *file, int line, const char *unit) +{ + double std_dev = 0, mean_spr, mean_rps, med_spr, med_rps; + int i; + + if (bench_skip) + goto done; + + if (bench_nruns < 1) { + fprintf(stderr, "\n%s:%d benchmark %s never ran", file, line, + bench_name); + abort(); + } + /* median */ + qsort(bench_samples, bench_nruns, sizeof(*bench_samples), tscmp); + i = bench_nruns / 2; + med_spr = tstod(&bench_samples[i]); + if (bench_nruns > 1 && bench_nruns & 1) + med_spr = (med_spr + tstod(&bench_samples[i - 1])) / 2.0; + med_rps = (med_spr == 0.0) ? INFINITY : 1.0/med_spr; + /* mean */ + mean_spr = bench_accum_secs / (double)bench_nruns; + mean_rps = (mean_spr == 0.0) ? INFINITY : 1.0/mean_spr; + /* std. dev */ + std_dev = 0; + for (i = 0; i < bench_nruns; i++) { + std_dev = tstod(&bench_samples[i]) - mean_spr; + std_dev *= std_dev; + } + std_dev /= (double)bench_nruns; + std_dev = sqrt(std_dev); + if (benchmark_detail_statistics) { + printf("%s: %d runs in %0.3fs, %0.03f/%0.03f ms/%s " + "(mean/median), std.dev %0.03f ms, " + "%0.2f/%0.2f %s/s (mean/median)\n", + bench_name, bench_nruns, bench_accum_secs, + mean_spr * 1000, med_spr * 1000, unit, std_dev * 1000, + mean_rps, med_rps, unit); + } else { + printf("%-*s %0.2f %s/s\n", BENCH_COLUMN_WIDTH, + bench_name, med_rps, unit); + } + done: + bench_name = NULL; + bench_nruns = 0; + free(bench_samples); + bench_samples = NULL; + bench_skip = 0; +} diff --git a/regress/unittests/test_helper/test_helper.h b/regress/unittests/test_helper/test_helper.h index 66302201cec3..23338af38882 100644 --- a/regress/unittests/test_helper/test_helper.h +++ b/regress/unittests/test_helper/test_helper.h @@ -1,4 +1,4 @@ -/* $OpenBSD: test_helper.h,v 1.9 2018/10/17 23:28:05 djm Exp $ */ +/* $OpenBSD: test_helper.h,v 1.10 2025/04/15 04:00:42 djm Exp $ */ /* * Copyright (c) 2011 Damien Miller * @@ -39,6 +39,7 @@ typedef void (test_onerror_func_t)(void *); /* Supplied by test suite */ void tests(void); +void benchmarks(void); const char *test_data_file(const char *name); void test_start(const char *n); @@ -49,6 +50,7 @@ int test_is_verbose(void); int test_is_quiet(void); int test_is_fast(void); int test_is_slow(void); +int test_is_benchmark(void); void test_subtest_info(const char *fmt, ...) __attribute__((format(printf, 1, 2))); void ssl_err_check(const char *file, int line); @@ -285,6 +287,26 @@ void assert_u64(const char *file, int line, #define ASSERT_U64_GE(a1, a2) \ assert_u64(__FILE__, __LINE__, #a1, #a2, a1, a2, TEST_GE) +/* Benchmarking support */ +#define BENCH_START(name) \ + do { \ + bench_start(__FILE__, __LINE__, name); \ + while (!bench_done()) { \ + bench_case_start(__FILE__, __LINE__); \ + do { +#define BENCH_FINISH(unit) \ + } while (0); \ + bench_case_finish(__FILE__, __LINE__); \ + } \ + bench_finish(__FILE__, __LINE__, unit); \ + } while (0) + +void bench_start(const char *file, int line, const char *name); +void bench_case_start(const char *file, int line); +void bench_case_finish(const char *file, int line); +void bench_finish(const char *file, int line, const char *unit); +int bench_done(void); + /* Fuzzing support */ struct fuzz; diff --git a/regress/unittests/utf8/Makefile b/regress/unittests/utf8/Makefile index f8eec0484f8f..e89536500822 100644 --- a/regress/unittests/utf8/Makefile +++ b/regress/unittests/utf8/Makefile @@ -1,14 +1,15 @@ -# $OpenBSD: Makefile,v 1.5 2017/12/21 00:41:22 djm Exp $ +# $OpenBSD: Makefile,v 1.6 2025/04/15 04:00:42 djm Exp $ PROG=test_utf8 SRCS=tests.c # From usr.bin/ssh -SRCS+=utf8.c atomicio.c +SRCS+=utf8.c atomicio.c misc.c xmalloc.c match.c ssherr.c cleanup.c fatal.c +SRCS+=sshbuf.c sshbuf-getput-basic.c sshbuf-misc.c addr.c addrmatch.c log.c REGRESS_TARGETS=run-regress-${PROG} run-regress-${PROG}: ${PROG} - env ${TEST_ENV} ./${PROG} + env ${TEST_ENV} ./${PROG} ${UNITTEST_ARGS} .include diff --git a/regress/unittests/utf8/tests.c b/regress/unittests/utf8/tests.c index 8cf524ddb210..3fb63415e1ad 100644 --- a/regress/unittests/utf8/tests.c +++ b/regress/unittests/utf8/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.4 2017/02/19 00:11:29 djm Exp $ */ +/* $OpenBSD: tests.c,v 1.5 2025/04/15 04:00:42 djm Exp $ */ /* * Regress test for the utf8.h *mprintf() API * @@ -102,3 +102,9 @@ tests(void) one(0, "double_fit", "a\343\201\201", 7, 5, -1, "a\\343"); one(0, "double_spc", "a\343\201\201", 13, 13, 13, "a\\343\\201\\201"); } + +void +benchmarks(void) +{ + printf("no benchmarks\n"); +} From 1ec5b39f1f673beac039bb42c98a11aa2b08a0b2 Mon Sep 17 00:00:00 2001 From: "dtucker@openbsd.org" Date: Tue, 15 Apr 2025 09:22:25 +0000 Subject: [PATCH 03/68] upstream: Cast signalled_keydrop to int when logging to prevent warning on platforms where sig_atomic_t is not the same as int. bz#3811, patch from jlduran at gmail com. OpenBSD-Commit-ID: b6bc9e9006e7f81ade57d41a48623a4323deca6c --- ssh-agent.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssh-agent.c b/ssh-agent.c index c27c5a956f2c..55b9f44f4d4a 100644 --- a/ssh-agent.c +++ b/ssh-agent.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-agent.c,v 1.310 2025/02/18 08:02:48 djm Exp $ */ +/* $OpenBSD: ssh-agent.c,v 1.311 2025/04/15 09:22:25 dtucker Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2526,7 +2526,7 @@ main(int ac, char **av) } if (signalled_keydrop) { logit("signal %d received; removing all keys", - signalled_keydrop); + (int)signalled_keydrop); remove_all_identities(); signalled_keydrop = 0; } From 849c2fd894aa87a7e40c71e8d5bda5392b1205be Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Tue, 15 Apr 2025 21:58:49 +1000 Subject: [PATCH 04/68] Look for sqrt(), possibly in libm. The unit tests now use sqrt(), which in some platforms (notably DragonFlyBSD and Solaris) is not in libc but rather libm. Since only the unit tests use this, add TESTLIBS and if necessary put libm in it. --- Makefile.in | 2 +- configure.ac | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 5a7a1bd101e3..f15ac558aea7 100644 --- a/Makefile.in +++ b/Makefile.in @@ -557,7 +557,7 @@ regress-prep: ln -s `cd $(srcdir) && pwd`/regress/Makefile `pwd`/regress/Makefile REGRESSLIBS=libssh.a $(LIBCOMPAT) -TESTLIBS=$(LIBS) $(CHANNELLIBS) +TESTLIBS=$(LIBS) $(CHANNELLIBS) @TESTLIBS@ regress/modpipe$(EXEEXT): $(srcdir)/regress/modpipe.c $(REGRESSLIBS) $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(srcdir)/regress/modpipe.c \ diff --git a/configure.ac b/configure.ac index ee77a0484b19..bbddca00908b 100644 --- a/configure.ac +++ b/configure.ac @@ -1449,6 +1449,11 @@ AC_CHECK_FUNC([getspnam], , AC_SEARCH_LIBS([basename], [gen], [AC_DEFINE([HAVE_BASENAME], [1], [Define if you have the basename function.])]) +dnl sqrt() only used in unit tests. +AC_CHECK_FUNC([sqrt], , + [AC_CHECK_LIB([m], [sqrt], [TESTLIBS="$TESTLIBS -lm"])]) +AC_SUBST([TESTLIBS]) + dnl zlib defaults to enabled zlib=yes AC_ARG_WITH([zlib], From 46e52fdae08b89264a0b23f94391c2bf637def34 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Wed, 16 Apr 2025 22:29:17 +1000 Subject: [PATCH 05/68] Provide INFINITY if it's not provided. INFINITY is specified in c99, so define if not provided. --- configure.ac | 5 +++++ defines.h | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/configure.ac b/configure.ac index bbddca00908b..69b74add8d00 100644 --- a/configure.ac +++ b/configure.ac @@ -2280,6 +2280,11 @@ AC_CHECK_DECLS([offsetof], , , [ #include ]) +AC_CHECK_DECLS([INFINITY], , + AC_CHECK_DECLS(__builtin_inff), + [#include ] +) + # extra bits for select(2) AC_CHECK_DECLS([howmany, NFDBITS], [], [], [[ #include diff --git a/defines.h b/defines.h index d2baeb9407bd..3721ae16ef92 100644 --- a/defines.h +++ b/defines.h @@ -986,4 +986,11 @@ struct winsize { /* The ML-KEM768 implementation also uses C89 features */ # define USE_MLKEM768X25519 1 #endif + +#if defined(HAVE_DECL_INFINITY) && HAVE_DECL_INFINITY == 0 +# if defined(HAVE_DECL___BUILTIN_INFF) && HAVE_DECL___BUILTIN_INFF == 1 +# define INFINITY __builtin_inff() +# endif +#endif + #endif /* _DEFINES_H */ From 9b50cb171b5c56184ce6fa3994ce62f9882d2daf Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Thu, 17 Apr 2025 16:51:14 +1000 Subject: [PATCH 06/68] Add includes.h for new tests. Fixes builds on older platforms. --- regress/unittests/hostkeys/tests.c | 2 ++ regress/unittests/sshbuf/tests.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/regress/unittests/hostkeys/tests.c b/regress/unittests/hostkeys/tests.c index a14ba19b3abe..c6e17fad09cb 100644 --- a/regress/unittests/hostkeys/tests.c +++ b/regress/unittests/hostkeys/tests.c @@ -5,6 +5,8 @@ * Placed in the public domain */ +#include "includes.h" + #include #include "../test_helper/test_helper.h" diff --git a/regress/unittests/sshbuf/tests.c b/regress/unittests/sshbuf/tests.c index eb801fb3b302..95a34a8c8e97 100644 --- a/regress/unittests/sshbuf/tests.c +++ b/regress/unittests/sshbuf/tests.c @@ -5,6 +5,8 @@ * Placed in the public domain */ +#include "includes.h" + #include #include "../test_helper/test_helper.h" From 52bddbc1a7f53a1e5c871767913648eb639ac6d5 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Fri, 18 Apr 2025 08:10:32 +1000 Subject: [PATCH 07/68] Include time.h for clock_gettime(). --- regress/unittests/test_helper/test_helper.c | 1 + 1 file changed, 1 insertion(+) diff --git a/regress/unittests/test_helper/test_helper.c b/regress/unittests/test_helper/test_helper.c index b75f7701401b..7783d65a4f4a 100644 --- a/regress/unittests/test_helper/test_helper.c +++ b/regress/unittests/test_helper/test_helper.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #ifdef WITH_OPENSSL From c627b468d3b99e487e2b24c90958ae57e633d681 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Fri, 18 Apr 2025 08:14:16 +1000 Subject: [PATCH 08/68] cygwin-install-action now puts setup.exe on D: --- .github/setup_ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/setup_ci.sh b/.github/setup_ci.sh index f6c4a5c84fb5..05ac755a79de 100755 --- a/.github/setup_ci.sh +++ b/.github/setup_ci.sh @@ -184,7 +184,7 @@ while [ ! -z "$PACKAGES" ] && [ "$tries" -gt "0" ]; do fi ;; setup) - if /cygdrive/c/setup.exe -q -P `echo "$PACKAGES" | tr ' ' ,`; then + if /cygdrive/d/setup.exe -q -P `echo "$PACKAGES" | tr ' ' ,`; then PACKAGES="" fi ;; From 76631fdd04824c3e50ea6551d3611b1fe0216a41 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Fri, 18 Apr 2025 08:18:52 +1000 Subject: [PATCH 09/68] Add 10.0 branch to test status page. --- .github/ci-status.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/ci-status.md b/.github/ci-status.md index 68275715dfb1..d47e59e1e5ea 100644 --- a/.github/ci-status.md +++ b/.github/ci-status.md @@ -6,6 +6,10 @@ master : [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/openssh.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:openssh) [![Coverity Status](https://scan.coverity.com/projects/21341/badge.svg)](https://scan.coverity.com/projects/openssh-portable) +10.0 : +[![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg?branch=V_10_0)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml?query=branch:V_10_0) +[![C/C++ CI self-hosted](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml/badge.svg?branch=V_10_0)](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml?query=branch:V_10_0) + 9.9 : [![C/C++ CI](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml/badge.svg?branch=V_9_9)](https://github.com/openssh/openssh-portable/actions/workflows/c-cpp.yml?query=branch:V_9_9) [![C/C++ CI self-hosted](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml/badge.svg?branch=V_9_9)](https://github.com/openssh/openssh-portable-selfhosted/actions/workflows/selfhosted.yml?query=branch:V_9_9) From b5b405fee7f3e79d44e2d2971a4b6b4cc53f112e Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Sun, 20 Apr 2025 09:07:57 +1000 Subject: [PATCH 10/68] Set Windows permssions on regress dir. Prevents "unprotected private key file" error when running tests. --- .github/setup_ci.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/setup_ci.sh b/.github/setup_ci.sh index 05ac755a79de..e3e28da6f1b1 100755 --- a/.github/setup_ci.sh +++ b/.github/setup_ci.sh @@ -12,7 +12,15 @@ case "$host" in echo Setting CYGWIN system environment variable. setx CYGWIN "binmode" echo Removing extended ACLs so umask works as expected. + set -x setfacl -b . regress + icacls regress /c /t /q /Inheritance:d + icacls regress /c /t /q /Grant ${LOGNAME}:F + icacls regress /c /t /q /Remove:g "Authenticated Users" \ + BUILTIN\\Administrators BUILTIN Everyone System Users + takeown /F regress + icacls regress + set +x PACKAGES="$PACKAGES,autoconf,automake,cygwin-devel,gcc-core" PACKAGES="$PACKAGES,make,openssl,libssl-devel,zlib-devel" ;; From c991273c18afc490313a9f282383eaf59d9c13b9 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Wed, 30 Apr 2025 05:23:15 +0000 Subject: [PATCH 11/68] upstream: fix a out-of-bounds read if the known_hosts file is truncated after the hostname. Reported by the OpenAI Security Research Team ok deraadt@ OpenBSD-Commit-ID: c0b516d7c80c4779a403826f73bcd8adbbc54ebd --- hostfile.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hostfile.c b/hostfile.c index c5669c703735..a4a5a9a5e3a3 100644 --- a/hostfile.c +++ b/hostfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hostfile.c,v 1.95 2023/02/21 06:48:18 dtucker Exp $ */ +/* $OpenBSD: hostfile.c,v 1.96 2025/04/30 05:23:15 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -810,6 +810,12 @@ hostkeys_foreach_file(const char *path, FILE *f, hostkeys_foreach_fn *callback, /* Find the end of the host name portion. */ for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) ; + if (*cp2 == '\0') { + verbose_f("truncated line at %s:%lu", path, linenum); + if ((options & HKF_WANT_MATCH) == 0) + goto bad; + continue; + } lineinfo.hosts = cp; *cp2++ = '\0'; From e048230106fb3f5e7cc07abc311c6feb5f52fd05 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Wed, 30 Apr 2025 05:26:15 +0000 Subject: [PATCH 12/68] upstream: make writing known_hosts lines more atomic, by writing the entire line in one operation and using unbuffered stdio. Usually writes to this file are serialised on the "Are you sure you want to continue connecting?" prompt, but if host key checking is disabled and connections were being made with high concurrency then interleaved writes might have been possible. feedback/ok deraadt@ millert@ OpenBSD-Commit-ID: d11222b49dabe5cfe0937b49cb439ba3d4847b08 --- hostfile.c | 52 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/hostfile.c b/hostfile.c index a4a5a9a5e3a3..e941fc4509ad 100644 --- a/hostfile.c +++ b/hostfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hostfile.c,v 1.96 2025/04/30 05:23:15 djm Exp $ */ +/* $OpenBSD: hostfile.c,v 1.97 2025/04/30 05:26:15 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -434,7 +434,7 @@ lookup_marker_in_hostkeys(struct hostkeys *hostkeys, int want_marker) } static int -write_host_entry(FILE *f, const char *host, const char *ip, +format_host_entry(struct sshbuf *entry, const char *host, const char *ip, const struct sshkey *key, int store_hash) { int r, success = 0; @@ -449,22 +449,50 @@ write_host_entry(FILE *f, const char *host, const char *ip, free(lhost); return 0; } - fprintf(f, "%s ", hashed_host); - } else if (ip != NULL) - fprintf(f, "%s,%s ", lhost, ip); - else { - fprintf(f, "%s ", lhost); + if ((r = sshbuf_putf(entry, "%s ", hashed_host)) != 0) + fatal_fr(r, "sshbuf_putf"); + } else if (ip != NULL) { + if ((r = sshbuf_putf(entry, "%s,%s ", lhost, ip)) != 0) + fatal_fr(r, "sshbuf_putf"); + } else { + if ((r = sshbuf_putf(entry, "%s ", lhost)) != 0) + fatal_fr(r, "sshbuf_putf"); } free(hashed_host); free(lhost); - if ((r = sshkey_write(key, f)) == 0) + if ((r = sshkey_format_text(key, entry)) == 0) success = 1; else error_fr(r, "sshkey_write"); - fputc('\n', f); + if ((r = sshbuf_putf(entry, "\n")) != 0) + fatal_fr(r, "sshbuf_putf"); + /* If hashing is enabled, the IP address needs to go on its own line */ if (success && store_hash && ip != NULL) - success = write_host_entry(f, ip, NULL, key, 1); + success = format_host_entry(entry, ip, NULL, key, 1); + return success; +} + +static int +write_host_entry(FILE *f, const char *host, const char *ip, + const struct sshkey *key, int store_hash) +{ + int r, success = 0; + struct sshbuf *entry = NULL; + + if ((entry = sshbuf_new()) == NULL) + fatal_f("allocation failed"); + if ((r = format_host_entry(entry, host, ip, key, store_hash)) != 1) { + debug_f("failed to format host entry"); + goto out; + } + if ((r = fwrite(sshbuf_ptr(entry), sshbuf_len(entry), 1, f)) != 1) { + error_f("fwrite: %s", strerror(errno)); + goto out; + } + success = 1; + out: + sshbuf_free(entry); return success; } @@ -520,9 +548,9 @@ add_host_to_hostfile(const char *filename, const char *host, if (key == NULL) return 1; /* XXX ? */ hostfile_create_user_ssh_dir(filename, 0); - f = fopen(filename, "a+"); - if (!f) + if ((f = fopen(filename, "a+")) == NULL) return 0; + setvbuf(f, NULL, _IONBF, 0); /* Make sure we have a terminating newline. */ if (fseek(f, -1L, SEEK_END) == 0 && fgetc(f) != '\n') addnl = 1; From 566443b5f5d7bc4c5310313b4e46232760850c7a Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 5 May 2025 02:40:30 +0000 Subject: [PATCH 13/68] upstream: correct log messages; the reap function is used for more than just the preauth process now OpenBSD-Commit-ID: 768c5b674bd77802bb197c31dba78559f1174c02 --- monitor_wrap.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monitor_wrap.c b/monitor_wrap.c index bd900b2f06ec..c30a7902d386 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.c,v 1.138 2024/10/22 06:13:00 dtucker Exp $ */ +/* $OpenBSD: monitor_wrap.c,v 1.139 2025/05/05 02:40:30 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -126,17 +126,17 @@ mm_reap(void) } if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { - debug_f("preauth child exited with status %d", + debug_f("child exited with status %d", WEXITSTATUS(status)); cleanup_exit(255); } } else if (WIFSIGNALED(status)) { - error_f("preauth child terminated by signal %d", + error_f("child terminated by signal %d", WTERMSIG(status)); cleanup_exit(signal_is_crash(WTERMSIG(status)) ? EXIT_CHILD_CRASH : 255); } else { - error_f("preauth child terminated abnormally (status=0x%x)", + error_f("child terminated abnormally (status=0x%x)", status); cleanup_exit(EXIT_CHILD_CRASH); } From 80162f9d7e7eadca4ffd0bd1c015d38cb1821ab6 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 5 May 2025 02:48:06 +0000 Subject: [PATCH 14/68] upstream: Move agent listener sockets from /tmp to under ~/.ssh/agent for both ssh-agent(1) and forwarded sockets in sshd(8). This ensures processes (such as Firefox) that have restricted filesystem access that includes /tmp (via unveil(3)) do not have the ability to use keys in an agent. Moving the default directory has the consequence that the OS will no longer clean up stale agent sockets, so ssh-agent now gains this ability. To support $HOME on NFS, the socket path includes a truncated hash of the hostname. ssh-agent will by default only clean up sockets from the same hostname. ssh-agent gains some new flags: -U suppresses the automatic cleanup of stale sockets when it starts. -u forces a cleanup without keeping a running agent, -uu forces a cleanup that ignores the hostname. -T makes ssh-agent put the socket back in /tmp. feedback deraadt@ naddy@, doitdoitdoit deraadt@ OpenBSD-Commit-ID: 8383dabd98092fe5498d5f7f15c7d314b03a93e1 --- Makefile.in | 6 ++-- hostfile.c | 2 +- misc.c | 17 ++++++++++- misc.h | 8 ++++- pathnames.h | 9 +++++- session.c | 34 +++------------------ ssh-agent.1 | 32 ++++++++++++++++---- ssh-agent.c | 85 +++++++++++++++++++++++++++++++++++++++++++---------- 8 files changed, 136 insertions(+), 57 deletions(-) diff --git a/Makefile.in b/Makefile.in index f15ac558aea7..4550e17951f4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -140,7 +140,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rhosts.o auth-passwd.o \ auth2-gss.o gss-serv.o gss-serv-krb5.o \ loginrec.o auth-pam.o auth-shadow.o auth-sia.o \ sftp-server.o sftp-common.o \ - uidswap.o platform-listen.o $(SKOBJS) + uidswap.o platform-listen.o misc-agent.o $(SKOBJS) SSHD_AUTH_OBJS=sshd-auth.o \ auth2-methods.o \ @@ -155,7 +155,7 @@ SSHD_AUTH_OBJS=sshd-auth.o \ sandbox-null.o sandbox-rlimit.o sandbox-darwin.o \ sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-solaris.o \ sftp-server.o sftp-common.o \ - uidswap.o $(SKOBJS) + uidswap.o misc-agent.o $(SKOBJS) SFTP_CLIENT_OBJS=sftp-common.o sftp-client.o sftp-glob.o @@ -163,7 +163,7 @@ SCP_OBJS= scp.o progressmeter.o $(SFTP_CLIENT_OBJS) SSHADD_OBJS= ssh-add.o $(SKOBJS) -SSHAGENT_OBJS= ssh-agent.o ssh-pkcs11-client.o $(SKOBJS) +SSHAGENT_OBJS= ssh-agent.o ssh-pkcs11-client.o misc-agent.o $(SKOBJS) SSHKEYGEN_OBJS= ssh-keygen.o sshsig.o $(SKOBJS) diff --git a/hostfile.c b/hostfile.c index e941fc4509ad..4b4a0e31ef38 100644 --- a/hostfile.c +++ b/hostfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hostfile.c,v 1.97 2025/04/30 05:26:15 djm Exp $ */ +/* $OpenBSD: hostfile.c,v 1.98 2025/05/05 02:48:07 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland diff --git a/misc.c b/misc.c index dd0bd032ae3c..25465dcd2d4f 100644 --- a/misc.c +++ b/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.198 2024/10/24 03:14:37 djm Exp $ */ +/* $OpenBSD: misc.c,v 1.199 2025/05/05 02:48:06 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005-2020 Damien Miller. All rights reserved. @@ -3138,3 +3138,18 @@ signal_is_crash(int sig) } return 0; } + +char * +get_homedir(void) +{ + char *cp; + struct passwd *pw; + + if ((cp = getenv("HOME")) != NULL && *cp != '\0') + return xstrdup(cp); + + if ((pw = getpwuid(getuid())) != NULL && *pw->pw_dir != '\0') + return xstrdup(pw->pw_dir); + + return NULL; +} diff --git a/misc.h b/misc.h index efecdf1ad6f9..a7afa23e8e92 100644 --- a/misc.h +++ b/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.110 2024/09/25 01:24:04 djm Exp $ */ +/* $OpenBSD: misc.h,v 1.111 2025/05/05 02:48:06 djm Exp $ */ /* * Author: Tatu Ylonen @@ -108,6 +108,7 @@ int parse_pattern_interval(const char *, char **, int *); int path_absolute(const char *); int stdfd_devnull(int, int, int); int lib_contains_symbol(const char *, const char *); +char *get_homedir(void); void sock_set_v6only(int); @@ -231,6 +232,11 @@ int ptimeout_get_ms(struct timespec *pt); struct timespec *ptimeout_get_tsp(struct timespec *pt); int ptimeout_isset(struct timespec *pt); +/* misc-agent.c */ +char *agent_hostname_hash(void); +int agent_listener(const char *, const char *, int *, char **); +void agent_cleanup_stale(const char *, int); + /* readpass.c */ #define RP_ECHO 0x0001 diff --git a/pathnames.h b/pathnames.h index 1158bec96781..e07395cb6f26 100644 --- a/pathnames.h +++ b/pathnames.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pathnames.h,v 1.32 2024/05/17 00:30:24 djm Exp $ */ +/* $OpenBSD: pathnames.h,v 1.34 2025/05/05 02:48:06 djm Exp $ */ /* * Author: Tatu Ylonen @@ -67,6 +67,13 @@ */ #define _PATH_SSH_USER_DIR ".ssh" + +/* + * The directory in which ssh-agent sockets and agent sockets forwarded by + * sshd reside. This directory should not be world-readable. + */ +#define _PATH_SSH_AGENT_SOCKET_DIR _PATH_SSH_USER_DIR "/agent" + /* * Per-user file containing host keys of known hosts. This file need not be * readable by anyone except the user him/herself, though this does not diff --git a/session.c b/session.c index 6444c77f31c2..630e0e6a353c 100644 --- a/session.c +++ b/session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: session.c,v 1.341 2025/04/09 07:00:03 djm Exp $ */ +/* $OpenBSD: session.c,v 1.342 2025/05/05 02:48:06 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -175,7 +175,6 @@ static char *auth_info_file = NULL; /* Name and directory of socket for authentication agent forwarding. */ static char *auth_sock_name = NULL; -static char *auth_sock_dir = NULL; /* removes the agent forwarding socket */ @@ -185,7 +184,6 @@ auth_sock_cleanup_proc(struct passwd *pw) if (auth_sock_name != NULL) { temporarily_use_uid(pw); unlink(auth_sock_name); - rmdir(auth_sock_dir); auth_sock_name = NULL; restore_uid(); } @@ -205,32 +203,15 @@ auth_input_request_forwarding(struct ssh *ssh, struct passwd * pw) /* Temporarily drop privileged uid for mkdir/bind. */ temporarily_use_uid(pw); - /* Allocate a buffer for the socket name, and format the name. */ - auth_sock_dir = xstrdup("/tmp/ssh-XXXXXXXXXX"); - - /* Create private directory for socket */ - if (mkdtemp(auth_sock_dir) == NULL) { + if (agent_listener(pw->pw_dir, "sshd", &sock, &auth_sock_name) != 0) { + /* a more detailed error is already logged */ ssh_packet_send_debug(ssh, "Agent forwarding disabled: " - "mkdtemp() failed: %.100s", strerror(errno)); + "couldn't create listener socket"); restore_uid(); - free(auth_sock_dir); - auth_sock_dir = NULL; goto authsock_err; } - - xasprintf(&auth_sock_name, "%s/agent.%ld", - auth_sock_dir, (long) getpid()); - - /* Start a Unix listener on auth_sock_name. */ - sock = unix_listener(auth_sock_name, SSH_LISTEN_BACKLOG, 0); - - /* Restore the privileged uid. */ restore_uid(); - /* Check for socket/bind/listen failure. */ - if (sock < 0) - goto authsock_err; - /* Allocate a channel for the authentication agent socket. */ nc = channel_new(ssh, "auth-listener", SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1, @@ -241,16 +222,9 @@ auth_input_request_forwarding(struct ssh *ssh, struct passwd * pw) authsock_err: free(auth_sock_name); - if (auth_sock_dir != NULL) { - temporarily_use_uid(pw); - rmdir(auth_sock_dir); - restore_uid(); - free(auth_sock_dir); - } if (sock != -1) close(sock); auth_sock_name = NULL; - auth_sock_dir = NULL; return 0; } diff --git a/ssh-agent.1 b/ssh-agent.1 index 533ad6d3a6d2..3b515ac42384 100644 --- a/ssh-agent.1 +++ b/ssh-agent.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-agent.1,v 1.82 2025/02/09 18:24:08 schwarze Exp $ +.\" $OpenBSD: ssh-agent.1,v 1.83 2025/05/05 02:48:06 djm Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -34,7 +34,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: February 9 2025 $ +.Dd $Mdocdate: May 5 2025 $ .Dt SSH-AGENT 1 .Os .Sh NAME @@ -43,13 +43,14 @@ .Sh SYNOPSIS .Nm ssh-agent .Op Fl c | s -.Op Fl \&Dd +.Op Fl \&DdTU .Op Fl a Ar bind_address .Op Fl E Ar fingerprint_hash .Op Fl O Ar option .Op Fl P Ar allowed_providers .Op Fl t Ar life .Nm ssh-agent +.Op Fl TU .Op Fl a Ar bind_address .Op Fl E Ar fingerprint_hash .Op Fl O Ar option @@ -59,6 +60,8 @@ .Nm ssh-agent .Op Fl c | s .Fl k +.Nm ssh-agent +.Fl u .Sh DESCRIPTION .Nm is a program to hold private keys used for public key authentication. @@ -74,8 +77,8 @@ Bind the agent to the .Ux Ns -domain socket .Ar bind_address . -The default is -.Pa $TMPDIR/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt . +The default is to create a socket at a random path matching +.Pa $HOME/.ssh/agent/s.* .It Fl c Generate C-shell commands on standard output. This is the default if @@ -173,6 +176,11 @@ Generate Bourne shell commands on standard output. This is the default if .Ev SHELL does not look like it's a csh style of shell. +.It Fl T +Bind the agent socket in a randomised subdirectory of the form +.Pa $TMPDIR/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt , +instead of the default behaviour of using a randomised name matching +.Pa $HOME/.ssh/agent/s.* . .It Fl t Ar life Set a default value for the maximum lifetime of identities added to the agent. The lifetime may be specified in seconds or in a time format specified in @@ -186,6 +194,20 @@ If a command (and optional arguments) is given, this is executed as a subprocess of the agent. The agent exits automatically when the command given on the command line terminates. +.It Fl U +Instructs +.Nm +not to clean up stale agent sockets under +.Pa $HOME/.ssh/agent/ . +.It Fl u +Instructs +.Nm +to only clean up stale agent sockets under +.Pa $HOME/.ssh/agent/ +and then exit immediately. +If this option is given twice, +.Nm +will delete stale agent sockets regardless of the host name that created them. .El .Pp There are three main ways to get an agent set up. diff --git a/ssh-agent.c b/ssh-agent.c index 55b9f44f4d4a..8a88ef3fd1c0 100644 --- a/ssh-agent.c +++ b/ssh-agent.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-agent.c,v 1.311 2025/04/15 09:22:25 dtucker Exp $ */ +/* $OpenBSD: ssh-agent.c,v 1.312 2025/05/05 02:48:06 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2208,20 +2208,23 @@ static void usage(void) { fprintf(stderr, - "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" + "usage: ssh-agent [-c | -s] [-DdTU] [-a bind_address] [-E fingerprint_hash]\n" " [-O option] [-P allowed_providers] [-t life]\n" - " ssh-agent [-a bind_address] [-E fingerprint_hash] [-O option]\n" + " ssh-agent [-TU] [-a bind_address] [-E fingerprint_hash] [-O option]\n" " [-P allowed_providers] [-t life] command [arg ...]\n" - " ssh-agent [-c | -s] -k\n"); + " ssh-agent [-c | -s] -k\n" + " ssh-agent -u\n"); exit(1); } int main(int ac, char **av) { - int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0; + int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0; + int s_flag = 0, T_flag = 0, u_flag = 0, U_flag = 0; int sock = -1, ch, result, saved_errno; - char *shell, *format, *fdstr, *pidstr, *agentsocket = NULL; + char *homedir = NULL, *shell, *format, *pidstr, *agentsocket = NULL; + char *fdstr; const char *errstr = NULL; const char *ccp; #ifdef HAVE_SETRLIMIT @@ -2256,7 +2259,7 @@ main(int ac, char **av) __progname = ssh_get_progname(av[0]); seed_rng(); - while ((ch = getopt(ac, av, "cDdksE:a:O:P:t:")) != -1) { + while ((ch = getopt(ac, av, "cDdksTuUE:a:O:P:t:")) != -1) { switch (ch) { case 'E': fingerprint_hash = ssh_digest_alg_by_name(optarg); @@ -2313,6 +2316,15 @@ main(int ac, char **av) usage(); } break; + case 'T': + T_flag++; + break; + case 'u': + u_flag++; + break; + case 'U': + U_flag++; + break; default: usage(); } @@ -2320,9 +2332,14 @@ main(int ac, char **av) ac -= optind; av += optind; - if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) + if (ac > 0 && + (c_flag || k_flag || s_flag || d_flag || D_flag || u_flag)) usage(); + log_init(__progname, + d_flag ? SYSLOG_LEVEL_DEBUG3 : SYSLOG_LEVEL_INFO, + SYSLOG_FACILITY_AUTH, 1); + if (allowed_providers == NULL) allowed_providers = xstrdup(DEFAULT_ALLOWED_PROVIDERS); if (websafe_allowlist == NULL) @@ -2358,6 +2375,14 @@ main(int ac, char **av) printf("echo Agent pid %ld killed;\n", (long)pid); exit(0); } + if (u_flag) { + if ((homedir = get_homedir()) == NULL) + fatal("Couldn't determine home directory"); + agent_cleanup_stale(homedir, u_flag > 1); + printf("Deleted stale agent sockets in ~/%s\n", + _PATH_SSH_AGENT_SOCKET_DIR); + exit(0); + } /* * Minimum file descriptors: @@ -2391,22 +2416,52 @@ main(int ac, char **av) sock = 3; } - /* Otherwise, create private directory for agent socket */ - if (sock == -1) { - if (agentsocket == NULL) { + if (sock == -1 && agentsocket == NULL && !T_flag) { + /* Default case: ~/.ssh/agent/[socket] */ + if ((homedir = get_homedir()) == NULL) + fatal("Couldn't determine home directory"); + if (!U_flag) + agent_cleanup_stale(homedir, 0); + if (agent_listener(homedir, "agent", &sock, &agentsocket) != 0) + fatal_f("Couldn't prepare agent socket"); + if (strlcpy(socket_name, agentsocket, + sizeof(socket_name)) >= sizeof(socket_name)) { + fatal_f("Socket path \"%s\" too long", + agentsocket); + } + free(homedir); + free(agentsocket); + agentsocket = NULL; + } else if (sock == -1) { + if (T_flag) { + /* + * Create private directory for agent socket + * in $TMPDIR. + */ mktemp_proto(socket_dir, sizeof(socket_dir)); if (mkdtemp(socket_dir) == NULL) { perror("mkdtemp: private socket dir"); exit(1); } - snprintf(socket_name, sizeof socket_name, - "%s/agent.%ld", socket_dir, - (long)parent_pid); + snprintf(socket_name, sizeof(socket_name), + "%s/agent.%ld", socket_dir, (long)parent_pid); } else { /* Try to use specified agent socket */ socket_dir[0] = '\0'; - strlcpy(socket_name, agentsocket, sizeof socket_name); + if (strlcpy(socket_name, agentsocket, + sizeof(socket_name)) >= sizeof(socket_name)) { + fatal_f("Socket path \"%s\" too long", + agentsocket); + } + } + /* Listen on socket */ + prev_mask = umask(0177); + if ((sock = unix_listener(socket_name, + SSH_LISTEN_BACKLOG, 0)) < 0) { + *socket_name = '\0'; /* Don't unlink existing file */ + cleanup_exit(1); } + umask(prev_mask); } closefrom(sock == -1 ? STDERR_FILENO + 1 : sock + 1); From 12912429cf39cfeca97dd18a8f875ad9824d1751 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 5 May 2025 03:35:06 +0000 Subject: [PATCH 15/68] upstream: missing file in previous commit OpenBSD-Commit-ID: e526c97fcb2fd9f0b7b229720972426ab437d7eb --- misc-agent.c | 329 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 misc-agent.c diff --git a/misc-agent.c b/misc-agent.c new file mode 100644 index 000000000000..d065ab0e5b7a --- /dev/null +++ b/misc-agent.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2025 Damien Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "digest.h" +#include "log.h" +#include "misc.h" +#include "pathnames.h" +#include "ssh.h" +#include "xmalloc.h" + +/* stuff shared by agent listeners (ssh-agent and sshd agent forwarding) */ + +#define SOCKET_HOSTNAME_HASHLEN 10 /* length of hostname hash in socket path */ + +/* used for presenting random strings in unix_listener_tmp and hostname_hash */ +static const char presentation_chars[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/* returns a text-encoded hash of the hostname of specified length (max 64) */ +static char * +hostname_hash(size_t len) +{ + char hostname[NI_MAXHOST], p[65]; + u_char hash[64]; + int r; + size_t l, i; + + l = ssh_digest_bytes(SSH_DIGEST_SHA512); + if (len > 64) { + error_f("bad length %zu > max %zd", len, l - 1); + return NULL; + } + if (gethostname(hostname, sizeof(hostname)) == -1) { + error_f("gethostname: %s", strerror(errno)); + return NULL; + } + if ((r = ssh_digest_memory(SSH_DIGEST_SHA512, + hostname, strlen(hostname), hash, sizeof(hash))) != 0) { + error_fr(r, "ssh_digest_memory"); + return NULL; + } + memset(p, '\0', sizeof(p)); + for (i = 0; i < l; i++) + p[i] = presentation_chars[ + hash[i] % (sizeof(presentation_chars) - 1)]; + /* debug3_f("hostname \"%s\" => hash \"%s\"", hostname, p); */ + p[len] = '\0'; + return xstrdup(p); +} + +char * +agent_hostname_hash(void) +{ + return hostname_hash(SOCKET_HOSTNAME_HASHLEN); +} + +/* + * Creates a unix listener at a mkstemp(3)-style path, e.g. "/dir/sock.XXXXXX" + * Supplied path is modified to the actual one used. + */ +static int +unix_listener_tmp(char *path, int backlog) +{ + struct sockaddr_un sunaddr; + int good, sock = -1; + size_t i, xstart; + mode_t prev_mask; + + /* Find first 'X' template character back from end of string */ + xstart = strlen(path); + while (xstart > 0 && path[xstart - 1] == 'X') + xstart--; + + memset(&sunaddr, 0, sizeof(sunaddr)); + sunaddr.sun_family = AF_UNIX; + prev_mask = umask(0177); + for (good = 0; !good;) { + sock = -1; + /* Randomise path suffix */ + for (i = xstart; path[i] != '\0'; i++) { + path[i] = presentation_chars[ + arc4random_uniform(sizeof(presentation_chars)-1)]; + } + debug_f("trying path \"%s\"", path); + + if (strlcpy(sunaddr.sun_path, path, + sizeof(sunaddr.sun_path)) >= sizeof(sunaddr.sun_path)) { + error_f("path \"%s\" too long for Unix domain socket", + path); + break; + } + + if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) { + error_f("socket: %.100s", strerror(errno)); + break; + } + if (bind(sock, (struct sockaddr *)&sunaddr, + sizeof(sunaddr)) == -1) { + if (errno == EADDRINUSE) { + error_f("bind \"%s\": %.100s", + path, strerror(errno)); + close(sock); + sock = -1; + continue; + } + error_f("bind \"%s\": %.100s", path, strerror(errno)); + break; + } + if (listen(sock, backlog) == -1) { + error_f("listen \"%s\": %s", path, strerror(errno)); + break; + } + good = 1; + } + umask(prev_mask); + if (good) { + debug3_f("listening on unix socket \"%s\" as fd=%d", + path, sock); + } else if (sock != -1) { + close(sock); + sock = -1; + } + return sock; +} + +/* + * Create a subdirectory under the supplied home directory if it + * doesn't already exist + */ +static int +ensure_mkdir(const char *homedir, const char *subdir) +{ + char *path; + + xasprintf(&path, "%s/%s", homedir, subdir); + if (mkdir(path, 0700) == 0) + debug("created directory %s", path); + else if (errno != EEXIST) { + error_f("mkdir %s: %s", path, strerror(errno)); + return -1; + } + free(path); + return 0; +} + +static int +agent_prepare_sockdir(const char *homedir) +{ + if (homedir == NULL || *homedir == '\0' || + ensure_mkdir(homedir, _PATH_SSH_USER_DIR) != 0 || + ensure_mkdir(homedir, _PATH_SSH_AGENT_SOCKET_DIR) != 0) + return -1; + return 0; +} + + +/* Get a path template for an agent socket in the user's homedir */ +static char * +agent_socket_template(const char *homedir, const char *tag) +{ + char *hostnamehash, *ret; + + if ((hostnamehash = hostname_hash(SOCKET_HOSTNAME_HASHLEN)) == NULL) + return NULL; + xasprintf(&ret, "%s/%s/s.%s.%s.XXXXXXXXXX", + homedir, _PATH_SSH_AGENT_SOCKET_DIR, hostnamehash, tag); + free(hostnamehash); + return ret; +} + +int +agent_listener(const char *homedir, const char *tag, int *sockp, char **pathp) +{ + int sock; + char *path; + + *sockp = -1; + *pathp = NULL; + + if (agent_prepare_sockdir(homedir) != 0) + return -1; /* error already logged */ + if ((path = agent_socket_template(homedir, tag)) == NULL) + return -1; /* error already logged */ + if ((sock = unix_listener_tmp(path, SSH_LISTEN_BACKLOG)) == -1) { + free(path); + return -1; /* error already logged */ + } + /* success */ + *sockp = sock; + *pathp = path; + return 0; +} + +static int +socket_is_stale(const char *path) +{ + int fd, r; + struct sockaddr_un sun; + socklen_t l = sizeof(r); + + /* attempt non-blocking connect on socket */ + memset(&sun, '\0', sizeof(sun)); + sun.sun_family = AF_UNIX; + if (strlcpy(sun.sun_path, path, + sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { + debug_f("path for \"%s\" too long for sockaddr_un", path); + return 0; + } + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) { + error_f("socket: %s", strerror(errno)); + return 0; + } + set_nonblock(fd); + /* a socket without a listener should yield an error immediately */ + if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + debug_f("connect \"%s\": %s", path, strerror(errno)); + close(fd); + return 1; + } + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &r, &l) == -1) { + debug_f("getsockopt: %s", strerror(errno)); + close(fd); + return 0; + } + if (r != 0) { + debug_f("socket error on %s: %s", path, strerror(errno)); + close(fd); + return 1; + } + close(fd); + debug_f("socket %s seems still active", path); + return 0; +} + +void +agent_cleanup_stale(const char *homedir, int ignore_hosthash) +{ + DIR *d; + struct dirent *dp; + struct stat sb; + char *prefix = NULL, *dirpath, *path; + struct timespec now, sub; + + /* Only consider sockets last modified > 1 hour ago */ + if (clock_gettime(CLOCK_REALTIME, &now) != 0) { + error_f("clock_gettime: %s", strerror(errno)); + return; + } + sub.tv_sec = 60 * 60; + sub.tv_nsec = 0; + timespecsub(&now, &sub, &now); + + /* Only consider sockets from the same hostname */ + if (!ignore_hosthash) { + if ((path = agent_hostname_hash()) == NULL) { + error_f("couldn't get hostname hash"); + return; + } + xasprintf(&prefix, "s.%s.", path); + free(path); + } + + xasprintf(&dirpath, "%s/%s", homedir, _PATH_SSH_AGENT_SOCKET_DIR); + if ((d = opendir(dirpath)) == NULL) { + if (errno != ENOENT) + error_f("opendir \"%s\": %s", dirpath, strerror(errno)); + free(dirpath); + return; + } + while ((dp = readdir(d)) != NULL) { + if (dp->d_type != DT_SOCK && dp->d_type != DT_UNKNOWN) + continue; + if (fstatat(dirfd(d), dp->d_name, + &sb, AT_SYMLINK_NOFOLLOW) != 0 && errno != ENOENT) { + error_f("stat \"%s/%s\": %s", + dirpath, dp->d_name, strerror(errno)); + continue; + } + if (!S_ISSOCK(sb.st_mode)) + continue; + if (timespeccmp(&sb.st_mtim, &now, >)) { + debug3_f("Ignoring recent socket \"%s/%s\"", + dirpath, dp->d_name); + continue; + } + if (!ignore_hosthash && + strncmp(dp->d_name, prefix, strlen(prefix)) != 0) { + debug3_f("Ignoring socket \"%s/%s\" " + "from different host", dirpath, dp->d_name); + continue; + } + xasprintf(&path, "%s/%s", dirpath, dp->d_name); + if (socket_is_stale(path)) { + debug_f("cleanup stale socket %s", path); + unlinkat(dirfd(d), dp->d_name, 0); + } + free(path); + } + closedir(d); + free(dirpath); + free(prefix); +} + From 6ab8133c067a8e91ba69ce7ca04f95b50f2f2d7b Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Mon, 5 May 2025 14:59:30 +1000 Subject: [PATCH 16/68] depend --- .depend | 1 + 1 file changed, 1 insertion(+) diff --git a/.depend b/.depend index 152905fb7b78..975d4427a2fb 100644 --- a/.depend +++ b/.depend @@ -79,6 +79,7 @@ loginrec.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-comp logintest.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h loginrec.h mac.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h digest.h hmac.h umac.h mac.h misc.h ssherr.h sshbuf.h openbsd-compat/openssl-compat.h match.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h match.h misc.h +misc-agent.o: digest.h log.h ssherr.h misc.h pathnames.h ssh.h xmalloc.h misc.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h misc.h log.h ssherr.h ssh.h sshbuf.h moduli.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h monitor.o: chacha.h poly1305.h cipher-aesctr.h rijndael.h kex.h mac.h crypto_api.h dh.h packet.h dispatch.h auth-options.h sshpty.h channels.h session.h sshlogin.h canohost.h log.h ssherr.h misc.h servconf.h monitor.h monitor_wrap.h monitor_fdpass.h compat.h ssh2.h authfd.h match.h sk-api.h srclimit.h From 7a7cc3cf721fe7fe9f4925d92bb7c694b8550a7f Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Mon, 5 May 2025 18:51:34 +1000 Subject: [PATCH 17/68] Cygwin install in back on D: --- .github/setup_ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/setup_ci.sh b/.github/setup_ci.sh index e3e28da6f1b1..9b2767a8514d 100755 --- a/.github/setup_ci.sh +++ b/.github/setup_ci.sh @@ -192,7 +192,7 @@ while [ ! -z "$PACKAGES" ] && [ "$tries" -gt "0" ]; do fi ;; setup) - if /cygdrive/d/setup.exe -q -P `echo "$PACKAGES" | tr ' ' ,`; then + if /cygdrive/c/setup.exe -q -P `echo "$PACKAGES" | tr ' ' ,`; then PACKAGES="" fi ;; From 7c0e6626e4be53efcfbb92f0c6382a76f1138e38 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Mon, 5 May 2025 19:08:48 +1000 Subject: [PATCH 18/68] includes.h for compat, time.h for clock_gettime. --- misc-agent.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/misc-agent.c b/misc-agent.c index d065ab0e5b7a..a8605302ef8c 100644 --- a/misc-agent.c +++ b/misc-agent.c @@ -14,6 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "includes.h" + #include #include #include @@ -25,6 +27,9 @@ #include #include #include +#ifdef HAVE_TIME_H +# include +#endif #include #include "digest.h" From 27861e9b15151898841097c14ee974c026093131 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Mon, 5 May 2025 19:09:25 +1000 Subject: [PATCH 19/68] Supply timespecsub if needed. --- defines.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/defines.h b/defines.h index 3721ae16ef92..a1bd6fad345e 100644 --- a/defines.h +++ b/defines.h @@ -515,6 +515,13 @@ struct winsize { } while (0) #endif +#ifndef timespeccmp +#define timespeccmp(tsp, usp, cmp) \ + (((tsp)->tv_sec == (usp)->tv_sec) ? \ + ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ + ((tsp)->tv_sec cmp (usp)->tv_sec)) +#endif + #ifndef TIMEVAL_TO_TIMESPEC #define TIMEVAL_TO_TIMESPEC(tv, ts) { \ (ts)->tv_sec = (tv)->tv_sec; \ From 61525ba967ac1bb7394ea0792aa6030bcbbad049 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Mon, 5 May 2025 20:45:42 +1000 Subject: [PATCH 20/68] Handle systems that don't have st_mtim. Ignores nanoseconds, but it's checking for >1h old so a few nanoseconds shouldn't matter much. Fixes build on Mac OS X. --- misc-agent.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/misc-agent.c b/misc-agent.c index a8605302ef8c..f13ccdf26b5d 100644 --- a/misc-agent.c +++ b/misc-agent.c @@ -270,7 +270,7 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash) struct dirent *dp; struct stat sb; char *prefix = NULL, *dirpath, *path; - struct timespec now, sub; + struct timespec now, sub, *mtimp = NULL; /* Only consider sockets last modified > 1 hour ago */ if (clock_gettime(CLOCK_REALTIME, &now) != 0) { @@ -309,7 +309,14 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash) } if (!S_ISSOCK(sb.st_mode)) continue; - if (timespeccmp(&sb.st_mtim, &now, >)) { +#ifdef HAVE_STRUCT_STAT_ST_MTIM + mtimp = &sb.st_mtim; +#else + sub.tv_sec = sb.st_mtime; + sub.tv_nsec = 0; + mtimp = ⊂ +#endif + if (timespeccmp(mtimp, &now, >)) { debug3_f("Ignoring recent socket \"%s/%s\"", dirpath, dp->d_name); continue; From 57eb87b15bd0343372f99d661ce95efb25a16f1e Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Tue, 6 May 2025 08:07:23 +1000 Subject: [PATCH 21/68] Boringssl now puts libcrypto in a different place. --- .github/setup_ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/setup_ci.sh b/.github/setup_ci.sh index 9b2767a8514d..df70c96ecb8a 100755 --- a/.github/setup_ci.sh +++ b/.github/setup_ci.sh @@ -248,7 +248,7 @@ if [ ! -z "${INSTALL_BORINGSSL}" ]; then cd ${HOME}/boringssl && mkdir build && cd build && cmake -GNinja -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && ninja && mkdir -p /opt/boringssl/lib && - cp ${HOME}/boringssl/build/crypto/libcrypto.a /opt/boringssl/lib && + cp ${HOME}/boringssl/build/libcrypto.a /opt/boringssl/lib && cp -r ${HOME}/boringssl/include /opt/boringssl) fi From d2480827b3ef6ec119965822afdff35d734b2dee Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Tue, 6 May 2025 08:15:34 +1000 Subject: [PATCH 22/68] New location of cygwin setup. --- .github/setup_ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/setup_ci.sh b/.github/setup_ci.sh index df70c96ecb8a..e4c7b041aa50 100755 --- a/.github/setup_ci.sh +++ b/.github/setup_ci.sh @@ -192,7 +192,7 @@ while [ ! -z "$PACKAGES" ] && [ "$tries" -gt "0" ]; do fi ;; setup) - if /cygdrive/c/setup.exe -q -P `echo "$PACKAGES" | tr ' ' ,`; then + if /cygdrive/d/cygwin/setup.exe -q -P `echo "$PACKAGES" | tr ' ' ,`; then PACKAGES="" fi ;; From 5fd6ef297dec23e3574646b6334087131230d0a6 Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Tue, 6 May 2025 19:01:00 +1000 Subject: [PATCH 23/68] Add minimal implementations of fstatat and unlinkat. Fixes build on some pre-POSIX.1-2008 platforms. --- configure.ac | 2 ++ openbsd-compat/bsd-misc.c | 40 +++++++++++++++++++++++++++++++++++++++ openbsd-compat/bsd-misc.h | 8 ++++++++ 3 files changed, 50 insertions(+) diff --git a/configure.ac b/configure.ac index 69b74add8d00..b19f790cdd54 100644 --- a/configure.ac +++ b/configure.ac @@ -2004,6 +2004,7 @@ AC_CHECK_FUNCS([ \ fnmatch \ freeaddrinfo \ freezero \ + fstatat \ fstatfs \ fstatvfs \ futimes \ @@ -2099,6 +2100,7 @@ AC_CHECK_FUNCS([ \ timegm \ timingsafe_bcmp \ truncate \ + unlinkat \ unsetenv \ updwtmpx \ utimensat \ diff --git a/openbsd-compat/bsd-misc.c b/openbsd-compat/bsd-misc.c index 226a5915bd1d..b26b4ba4670e 100644 --- a/openbsd-compat/bsd-misc.c +++ b/openbsd-compat/bsd-misc.c @@ -220,6 +220,46 @@ fchmodat(int fd, const char *path, mode_t mode, int flag) } #endif +#ifndef HAVE_FSTATAT +/* + * A limited implementation of fstatat that just has what OpenSSH uses: + * cwd-relative and absolute paths, with or without following symlinks. + */ +int +fstatat(int dirfd, const char *path, struct stat *sb, int flag) +{ + if (dirfd != AT_FDCWD && path && path[0] != '/') { + errno = ENOSYS; + return -1; + } + if (flag == 0) + return stat(path, sb); + else if (flag == AT_SYMLINK_NOFOLLOW) + return lstat(path, sb); + errno = ENOSYS; + return -1; +} +#endif + +#ifndef HAVE_UNLINKAT +/* + * A limited implementation of unlinkat that just has what OpenSSH uses: + * cwd-relative and absolute paths. + */ +int +unlinkat(int dirfd, const char *path, int flag) +{ + if (dirfd != AT_FDCWD && path && path[0] != '/') { + errno = ENOSYS; + return -1; + } + if (flag == 0) + return unlink(path); + errno = ENOSYS; + return -1; +} +#endif + #ifndef HAVE_TRUNCATE int truncate(const char *path, off_t length) { diff --git a/openbsd-compat/bsd-misc.h b/openbsd-compat/bsd-misc.h index 61ead1b7fad0..edb0fcc8ca2b 100644 --- a/openbsd-compat/bsd-misc.h +++ b/openbsd-compat/bsd-misc.h @@ -77,6 +77,14 @@ int fchmodat(int, const char *, mode_t, int); int fchownat(int, const char *, uid_t, gid_t, int); #endif +#ifdef HAVE_FSTATAT +int fstatat(int, const char *, struct stat *, int); +#endif + +#ifdef HAVE_UNLINKAT +int unlinkat(int, const char *, int); +#endif + #ifndef HAVE_TRUNCATE int truncate (const char *, off_t); #endif /* HAVE_TRUNCATE */ From fe883543bece18c975fa53aa02104f0433645d99 Mon Sep 17 00:00:00 2001 From: "jmc@openbsd.org" Date: Mon, 5 May 2025 05:47:28 +0000 Subject: [PATCH 24/68] upstream: - add full stop to the text in -a - move the -U and -u text to the correct place OpenBSD-Commit-ID: 2fb484337a0978c703f61983bb14bc5cbaf898c2 --- ssh-agent.1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ssh-agent.1 b/ssh-agent.1 index 3b515ac42384..9f56e3efdc08 100644 --- a/ssh-agent.1 +++ b/ssh-agent.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-agent.1,v 1.83 2025/05/05 02:48:06 djm Exp $ +.\" $OpenBSD: ssh-agent.1,v 1.84 2025/05/05 05:47:28 jmc Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -78,7 +78,7 @@ Bind the agent to the socket .Ar bind_address . The default is to create a socket at a random path matching -.Pa $HOME/.ssh/agent/s.* +.Pa $HOME/.ssh/agent/s.* . .It Fl c Generate C-shell commands on standard output. This is the default if @@ -189,11 +189,6 @@ A lifetime specified for an identity with .Xr ssh-add 1 overrides this value. Without this option the default maximum lifetime is forever. -.It Ar command Op Ar arg ... -If a command (and optional arguments) is given, -this is executed as a subprocess of the agent. -The agent exits automatically when the command given on the command -line terminates. .It Fl U Instructs .Nm @@ -208,6 +203,11 @@ and then exit immediately. If this option is given twice, .Nm will delete stale agent sockets regardless of the host name that created them. +.It Ar command Op Ar arg ... +If a command (and optional arguments) is given, +this is executed as a subprocess of the agent. +The agent exits automatically when the command given on the command +line terminates. .El .Pp There are three main ways to get an agent set up. From 928f8dcc1bb622c25be409c34374b655d0149373 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 5 May 2025 05:51:11 +0000 Subject: [PATCH 25/68] upstream: Now that there's an I-D for certificate keys, refer to that instead of the much more basic format description we had previously. OpenBSD-Commit-ID: cf01e0727a813fee8626ad7b3aa240621cc92014 --- PROTOCOL | 4 +- PROTOCOL.certkeys | 326 ---------------------------------------------- 2 files changed, 2 insertions(+), 328 deletions(-) delete mode 100644 PROTOCOL.certkeys diff --git a/PROTOCOL b/PROTOCOL index 26387793febc..a0a9837efe3e 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -41,7 +41,7 @@ https://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt OpenSSH introduces new public key algorithms to support certificate authentication for users and host keys. These methods are documented -in the file PROTOCOL.certkeys +in at https://datatracker.ietf.org/doc/draft-miller-ssh-cert/ 1.4. transport: Elliptic Curve cryptography @@ -792,4 +792,4 @@ master instance and later clients. OpenSSH extends the usual agent protocol. These changes are documented in the PROTOCOL.agent file. -$OpenBSD: PROTOCOL,v 1.55 2024/01/08 05:05:15 djm Exp $ +$OpenBSD: PROTOCOL,v 1.56 2025/05/05 05:51:11 djm Exp $ diff --git a/PROTOCOL.certkeys b/PROTOCOL.certkeys deleted file mode 100644 index 0a212c635c5d..000000000000 --- a/PROTOCOL.certkeys +++ /dev/null @@ -1,326 +0,0 @@ -This document describes a simple public-key certificate authentication -system for use by SSH. - -Background ----------- - -The SSH protocol currently supports a simple public key authentication -mechanism. Unlike other public key implementations, SSH eschews the use -of X.509 certificates and uses raw keys. This approach has some benefits -relating to simplicity of configuration and minimisation of attack -surface, but it does not support the important use-cases of centrally -managed, passwordless authentication and centrally certified host keys. - -These protocol extensions build on the simple public key authentication -system already in SSH to allow certificate-based authentication. The -certificates used are not traditional X.509 certificates, with numerous -options and complex encoding rules, but something rather more minimal: a -key, some identity information and usage options that have been signed -with some other trusted key. - -A sshd server may be configured to allow authentication via certified -keys, by extending the existing ~/.ssh/authorized_keys mechanism to -allow specification of certification authority keys in addition to -raw user keys. The ssh client will support automatic verification of -acceptance of certified host keys, by adding a similar ability to -specify CA keys in ~/.ssh/known_hosts. - -All certificate types include certification information along with the -public key that is used to sign challenges. In OpenSSH, ssh-keygen -performs the CA signing operation. - -Certified keys are represented using new key types: - - ssh-rsa-cert-v01@openssh.com - ssh-dss-cert-v01@openssh.com - ecdsa-sha2-nistp256-cert-v01@openssh.com - ecdsa-sha2-nistp384-cert-v01@openssh.com - ecdsa-sha2-nistp521-cert-v01@openssh.com - ssh-ed25519-cert-v01@openssh.com - -Two additional types exist for RSA certificates to force use of -SHA-2 signatures (SHA-256 and SHA-512 respectively): - - rsa-sha2-256-cert-v01@openssh.com - rsa-sha2-512-cert-v01@openssh.com - -These RSA/SHA-2 types should not appear in keys at rest or transmitted -on the wire, but do appear in a SSH_MSG_KEXINIT's host-key algorithms -field or in the "public key algorithm name" field of a "publickey" -SSH_USERAUTH_REQUEST to indicate that the signature will use the -specified algorithm. - -Protocol extensions -------------------- - -The SSH wire protocol includes several extensibility mechanisms. -These modifications shall take advantage of namespaced public key -algorithm names to add support for certificate authentication without -breaking the protocol - implementations that do not support the -extensions will simply ignore them. - -Authentication using the new key formats described below proceeds -using the existing SSH "publickey" authentication method described -in RFC4252 section 7. - -New public key formats ----------------------- - -The certificate key types take a similar high-level format (note: data -types and encoding are as per RFC4251 section 5). The serialised wire -encoding of these certificates is also used for storing them on disk. - -#define SSH_CERT_TYPE_USER 1 -#define SSH_CERT_TYPE_HOST 2 - -RSA certificate - - string "ssh-rsa-cert-v01@openssh.com" - string nonce - mpint e - mpint n - uint64 serial - uint32 type - string key id - string valid principals - uint64 valid after - uint64 valid before - string critical options - string extensions - string reserved - string signature key - string signature - -DSA certificate - - string "ssh-dss-cert-v01@openssh.com" - string nonce - mpint p - mpint q - mpint g - mpint y - uint64 serial - uint32 type - string key id - string valid principals - uint64 valid after - uint64 valid before - string critical options - string extensions - string reserved - string signature key - string signature - -ECDSA certificate - - string "ecdsa-sha2-nistp256-cert-v01@openssh.com" | - "ecdsa-sha2-nistp384-cert-v01@openssh.com" | - "ecdsa-sha2-nistp521-cert-v01@openssh.com" - string nonce - string curve - string public_key - uint64 serial - uint32 type - string key id - string valid principals - uint64 valid after - uint64 valid before - string critical options - string extensions - string reserved - string signature key - string signature - -ED25519 certificate - - string "ssh-ed25519-cert-v01@openssh.com" - string nonce - string pk - uint64 serial - uint32 type - string key id - string valid principals - uint64 valid after - uint64 valid before - string critical options - string extensions - string reserved - string signature key - string signature - -The nonce field is a CA-provided random bitstring of arbitrary length -(but typically 16 or 32 bytes) included to make attacks that depend on -inducing collisions in the signature hash infeasible. - -e and n are the RSA exponent and public modulus respectively. - -p, q, g, y are the DSA parameters as described in FIPS-186-2. - -curve and public key are respectively the ECDSA "[identifier]" and "Q" -defined in section 3.1 of RFC5656. - -pk is the encoded Ed25519 public key as defined by RFC8032. - -serial is an optional certificate serial number set by the CA to -provide an abbreviated way to refer to certificates from that CA. -If a CA does not wish to number its certificates, it must set this -field to zero. - -type specifies whether this certificate is for identification of a user -or a host using a SSH_CERT_TYPE_... value. - -key id is a free-form text field that is filled in by the CA at the time -of signing; the intention is that the contents of this field are used to -identify the identity principal in log messages. - -"valid principals" is a string containing zero or more principals as -strings packed inside it. These principals list the names for which this -certificate is valid; hostnames for SSH_CERT_TYPE_HOST certificates and -usernames for SSH_CERT_TYPE_USER certificates. As a special case, a -zero-length "valid principals" field means the certificate is valid for -any principal of the specified type. - -"valid after" and "valid before" specify a validity period for the -certificate. Each represents a time in seconds since 1970-01-01 -00:00:00. A certificate is considered valid if: - - valid after <= current time < valid before - -critical options is a set of zero or more key options encoded as -below. All such options are "critical" in the sense that an implementation -must refuse to authorise a key that has an unrecognised option. - -extensions is a set of zero or more optional extensions. These extensions -are not critical, and an implementation that encounters one that it does -not recognise may safely ignore it. - -Generally, critical options are used to control features that restrict -access where extensions are used to enable features that grant access. -This ensures that certificates containing unknown restrictions do not -inadvertently grant access while allowing new protocol features to be -enabled via extensions without breaking certificates' backwards -compatibility. - -The reserved field is currently unused and is ignored in this version of -the protocol. - -The signature key field contains the CA key used to sign the -certificate. The valid key types for CA keys are ssh-rsa, -ssh-dss, ssh-ed25519 and the ECDSA types ecdsa-sha2-nistp256, -ecdsa-sha2-nistp384, ecdsa-sha2-nistp521. "Chained" certificates, where -the signature key type is a certificate type itself are NOT supported. -Note that it is possible for a RSA certificate key to be signed by a -Ed25519 or ECDSA CA key and vice-versa. - -signature is computed over all preceding fields from the initial string -up to, and including the signature key. Signatures are computed and -encoded according to the rules defined for the CA's public key algorithm -(RFC4253 section 6.6 for ssh-rsa and ssh-dss, RFC5656 for the ECDSA -types, and RFC8032 for Ed25519). - -Critical options ----------------- - -The critical options section of the certificate specifies zero or more -options on the certificate's validity. The format of this field -is a sequence of zero or more tuples: - - string name - string data - -Options must be lexically ordered by "name" if they appear in the -sequence. Each named option may only appear once in a certificate. - -The name field identifies the option. The data field contains -option-specific information encoded as zero or more values inside -the string. I.e. an empty data field would be encoded as a zero- -length string (00 00 00 00), and data field that holds a single -string value "a" would be encoded as (00 00 00 05 00 00 00 01 65). - -All options are "critical"; if an implementation does not recognise -a option, then the validating party should refuse to accept the -certificate. - -Custom options should append the originating author or organisation's -domain name to the option name, e.g. "my-option@example.com". - -No critical options are defined for host certificates at present. The -supported user certificate options and the contents and structure of -their data fields are: - -Name Format Description ------------------------------------------------------------------------------ -force-command string Specifies a command that is executed - (replacing any the user specified on the - ssh command-line) whenever this key is - used for authentication. - -source-address string Comma-separated list of source addresses - from which this certificate is accepted - for authentication. Addresses are - specified in CIDR format (nn.nn.nn.nn/nn - or hhhh::hhhh/nn). - If this option is not present, then - certificates may be presented from any - source address. - -verify-required empty Flag indicating that signatures made - with this certificate must assert FIDO - user verification (e.g. PIN or - biometric). This option only makes sense - for the U2F/FIDO security key types that - support this feature in their signature - formats. - -Extensions ----------- - -The extensions section of the certificate specifies zero or more -non-critical certificate extensions. The encoding and ordering of -extensions in this field is identical to that of the critical options, -as is the requirement that each name appear only once. - -If an implementation does not recognise an extension, then it should -ignore it. - -Custom options should append the originating author or organisation's -domain name to the option name, e.g. "my-option@example.com". - -No extensions are defined for host certificates at present. The -supported user certificate extensions and the contents and structure of -their data fields are: - -Name Format Description ------------------------------------------------------------------------------ -no-touch-required empty Flag indicating that signatures made - with this certificate need not assert - FIDO user presence. This option only - makes sense for the U2F/FIDO security - key types that support this feature in - their signature formats. - -permit-X11-forwarding empty Flag indicating that X11 forwarding - should be permitted. X11 forwarding will - be refused if this option is absent. - -permit-agent-forwarding empty Flag indicating that agent forwarding - should be allowed. Agent forwarding - must not be permitted unless this - option is present. - -permit-port-forwarding empty Flag indicating that port-forwarding - should be allowed. If this option is - not present, then no port forwarding will - be allowed. - -permit-pty empty Flag indicating that PTY allocation - should be permitted. In the absence of - this option PTY allocation will be - disabled. - -permit-user-rc empty Flag indicating that execution of - ~/.ssh/rc should be permitted. Execution - of this script will not be permitted if - this option is not present. - -$OpenBSD: PROTOCOL.certkeys,v 1.20 2024/12/06 16:02:12 djm Exp $ From a32d28d792567253bb601362f36391f155f8f772 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Tue, 6 May 2025 05:40:56 +0000 Subject: [PATCH 26/68] upstream: finally remove DSA signature support from OpenSSH. feedback/ok tb@, ok deraadt@ OpenBSD-Commit-ID: bfe6ee73c1b676c81a2901030c791f8ec888228f --- .depend | 3 +- Makefile.in | 2 +- PROTOCOL | 11 +- TODO | 2 +- authfd.c | 4 +- authfile.c | 3 +- configure.ac | 1 - contrib/cygwin/ssh-user-config | 3 +- dns.c | 5 +- hostfile.c | 6 +- openbsd-compat/openssl-compat.h | 3 - pathnames.h | 4 +- readconf.c | 5 +- ssh-add.c | 5 +- ssh-dss.c | 457 -------------------------------- ssh-keygen.c | 89 +------ ssh-keyscan.c | 26 +- ssh-keysign.c | 5 +- ssh.c | 8 +- ssh_config | 3 +- sshconnect.c | 5 +- sshd-auth.c | 5 +- sshd-session.c | 3 +- sshd.c | 3 +- sshkey.c | 42 +-- sshkey.h | 11 +- 26 files changed, 41 insertions(+), 673 deletions(-) delete mode 100644 ssh-dss.c diff --git a/.depend b/.depend index 975d4427a2fb..3e64eb84a6a5 100644 --- a/.depend +++ b/.depend @@ -79,7 +79,7 @@ loginrec.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-comp logintest.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h loginrec.h mac.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h digest.h hmac.h umac.h mac.h misc.h ssherr.h sshbuf.h openbsd-compat/openssl-compat.h match.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h match.h misc.h -misc-agent.o: digest.h log.h ssherr.h misc.h pathnames.h ssh.h xmalloc.h +misc-agent.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h digest.h log.h ssherr.h misc.h pathnames.h ssh.h xmalloc.h misc.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h misc.h log.h ssherr.h ssh.h sshbuf.h moduli.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h monitor.o: chacha.h poly1305.h cipher-aesctr.h rijndael.h kex.h mac.h crypto_api.h dh.h packet.h dispatch.h auth-options.h sshpty.h channels.h session.h sshlogin.h canohost.h log.h ssherr.h misc.h servconf.h monitor.h monitor_wrap.h monitor_fdpass.h compat.h ssh2.h authfd.h match.h sk-api.h srclimit.h @@ -129,7 +129,6 @@ sntrup761.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-com srclimit.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ./openbsd-compat/sys-tree.h addr.h canohost.h log.h ssherr.h misc.h srclimit.h xmalloc.h servconf.h openbsd-compat/sys-queue.h match.h ssh-add.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h xmalloc.h ssh.h log.h ssherr.h sshkey.h sshbuf.h authfd.h authfile.h pathnames.h misc.h digest.h ssh-sk.h sk-api.h hostfile.h ssh-agent.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h openbsd-compat/sys-queue.h xmalloc.h ssh.h ssh2.h sshbuf.h sshkey.h authfd.h log.h ssherr.h misc.h digest.h match.h msg.h pathnames.h ssh-pkcs11.h sk-api.h myproposal.h -ssh-dss.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ssh-ecdsa-sk.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h openbsd-compat/openssl-compat.h sshbuf.h ssherr.h digest.h sshkey.h ssh-ecdsa.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h ssh-ed25519-sk.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-compat.h openbsd-compat/base64.h openbsd-compat/sigact.h openbsd-compat/readpassphrase.h openbsd-compat/vis.h openbsd-compat/getrrsetbyname.h openbsd-compat/sha1.h openbsd-compat/sha2.h openbsd-compat/md5.h openbsd-compat/blf.h openbsd-compat/fnmatch.h openbsd-compat/getopt.h openbsd-compat/bsd-signal.h openbsd-compat/bsd-misc.h openbsd-compat/bsd-setres_id.h openbsd-compat/bsd-statvfs.h openbsd-compat/bsd-waitpid.h openbsd-compat/bsd-poll.h openbsd-compat/fake-rfc2553.h openbsd-compat/bsd-cygwin_util.h openbsd-compat/port-aix.h openbsd-compat/port-irix.h openbsd-compat/port-linux.h openbsd-compat/port-solaris.h openbsd-compat/port-net.h openbsd-compat/port-uw.h openbsd-compat/bsd-nextstep.h entropy.h crypto_api.h log.h ssherr.h sshbuf.h sshkey.h ssh.h digest.h diff --git a/Makefile.in b/Makefile.in index 4550e17951f4..ae86a712b17b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -105,7 +105,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ log.o match.o moduli.o nchan.o packet.o \ readpass.o ttymodes.o xmalloc.o addr.o addrmatch.o \ atomicio.o dispatch.o mac.o misc.o utf8.o \ - monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-ecdsa-sk.o \ + monitor_fdpass.o rijndael.o ssh-ecdsa.o ssh-ecdsa-sk.o \ ssh-ed25519-sk.o ssh-rsa.o dh.o \ msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \ ssh-pkcs11.o smult_curve25519_ref.o \ diff --git a/PROTOCOL b/PROTOCOL index a0a9837efe3e..f99173c527b7 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -34,7 +34,6 @@ The method is documented in: https://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt 1.3. transport: New public key algorithms "ssh-rsa-cert-v01@openssh.com", - "ssh-dsa-cert-v01@openssh.com", "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ecdsa-sha2-nistp384-cert-v01@openssh.com" and "ecdsa-sha2-nistp521-cert-v01@openssh.com" @@ -765,15 +764,15 @@ authorized_keys files, are formatted as a single line of text consisting of the public key algorithm name followed by a base64-encoded key blob. The public key blob (before base64 encoding) is the same format used for the encoding of public keys sent on the wire: as described in RFC4253 -section 6.6 for RSA and DSA keys, RFC5656 section 3.1 for ECDSA keys -and the "New public key formats" section of PROTOCOL.certkeys for the -OpenSSH certificate formats. +section 6.6 for RSA keys, RFC5656 section 3.1 for ECDSA keys and +https://datatracker.ietf.org/doc/draft-miller-ssh-cert/ +for the OpenSSH certificate formats. 5.2 Private key format OpenSSH private keys, as generated by ssh-keygen(1) use the format described in PROTOCOL.key by default. As a legacy option, PEM format -(RFC7468) private keys are also supported for RSA, DSA and ECDSA keys +(RFC7468) private keys are also supported for RSA and ECDSA keys and were the default format before OpenSSH 7.8. 5.3 KRL format @@ -792,4 +791,4 @@ master instance and later clients. OpenSSH extends the usual agent protocol. These changes are documented in the PROTOCOL.agent file. -$OpenBSD: PROTOCOL,v 1.56 2025/05/05 05:51:11 djm Exp $ +$OpenBSD: PROTOCOL,v 1.57 2025/05/06 05:40:56 djm Exp $ diff --git a/TODO b/TODO index b76529c960a0..e9e2d96e6e1d 100644 --- a/TODO +++ b/TODO @@ -7,7 +7,7 @@ Documentation: - Install FAQ? -- General FAQ on S/Key, TIS, RSA, RSA2, DSA, etc and suggestions on when it +- General FAQ on S/Key, TIS, RSA, RSA2, etc and suggestions on when it would be best to use them. - Create a Documentation/ directory? diff --git a/authfd.c b/authfd.c index e04ad0cf2d02..66797880af84 100644 --- a/authfd.c +++ b/authfd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: authfd.c,v 1.134 2023/12/18 14:46:56 djm Exp $ */ +/* $OpenBSD: authfd.c,v 1.135 2025/05/06 05:40:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -601,8 +601,6 @@ ssh_add_identity_constrained(int sock, struct sshkey *key, #ifdef WITH_OPENSSL case KEY_RSA: case KEY_RSA_CERT: - case KEY_DSA: - case KEY_DSA_CERT: case KEY_ECDSA: case KEY_ECDSA_CERT: case KEY_ECDSA_SK: diff --git a/authfile.c b/authfile.c index 4a2f21eb7f6d..bf485baf237a 100644 --- a/authfile.c +++ b/authfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: authfile.c,v 1.145 2024/09/22 12:56:21 jsg Exp $ */ +/* $OpenBSD: authfile.c,v 1.146 2025/05/06 05:40:56 djm Exp $ */ /* * Copyright (c) 2000, 2013 Markus Friedl. All rights reserved. * @@ -328,7 +328,6 @@ sshkey_load_private_cert(int type, const char *filename, const char *passphrase, switch (type) { #ifdef WITH_OPENSSL case KEY_RSA: - case KEY_DSA: case KEY_ECDSA: #endif /* WITH_OPENSSL */ case KEY_ED25519: diff --git a/configure.ac b/configure.ac index b19f790cdd54..221d5f5618bf 100644 --- a/configure.ac +++ b/configure.ac @@ -3078,7 +3078,6 @@ if test "x$openssl" = "xyes" ; then AC_CHECK_FUNCS([ \ BN_is_prime_ex \ DES_crypt \ - DSA_generate_parameters_ex \ EVP_DigestSign \ EVP_DigestVerify \ EVP_DigestFinal_ex \ diff --git a/contrib/cygwin/ssh-user-config b/contrib/cygwin/ssh-user-config index 3858722f646d..35802d06ecba 100644 --- a/contrib/cygwin/ssh-user-config +++ b/contrib/cygwin/ssh-user-config @@ -246,9 +246,8 @@ done check_user_homedir check_user_dot_ssh_dir create_identity id_rsa rsa "SSH2 RSA" -create_identity id_dsa dsa "SSH2 DSA" +create_identity id_ed25519 ed25519 "SSH2 Ed25519" create_identity id_ecdsa ecdsa "SSH2 ECDSA" -create_identity identity rsa1 "(deprecated) SSH1 RSA" fix_authorized_keys_perms echo diff --git a/dns.c b/dns.c index 939241440777..c01c7bebee18 100644 --- a/dns.c +++ b/dns.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dns.c,v 1.44 2023/03/10 04:06:21 dtucker Exp $ */ +/* $OpenBSD: dns.c,v 1.45 2025/05/06 05:40:56 djm Exp $ */ /* * Copyright (c) 2003 Wesley Griffin. All rights reserved. @@ -88,9 +88,6 @@ dns_read_key(u_int8_t *algorithm, u_int8_t *digest_type, case KEY_RSA: *algorithm = SSHFP_KEY_RSA; break; - case KEY_DSA: - *algorithm = SSHFP_KEY_DSA; - break; case KEY_ECDSA: *algorithm = SSHFP_KEY_ECDSA; break; diff --git a/hostfile.c b/hostfile.c index 4b4a0e31ef38..4cec57da50c6 100644 --- a/hostfile.c +++ b/hostfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hostfile.c,v 1.98 2025/05/05 02:48:07 djm Exp $ */ +/* $OpenBSD: hostfile.c,v 1.99 2025/05/06 05:40:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -150,8 +150,8 @@ host_hash(const char *host, const char *name_from_hostfile, u_int src_len) } /* - * Parses an RSA (number of bits, e, n) or DSA key from a string. Moves the - * pointer over the key. Skips any whitespace at the beginning and at end. + * Parses an RSA key from a string. Moves the pointer over the key. + * Skips any whitespace at the beginning and at end. */ int diff --git a/openbsd-compat/openssl-compat.h b/openbsd-compat/openssl-compat.h index 6b8fff412951..a7c41c235b5c 100644 --- a/openbsd-compat/openssl-compat.h +++ b/openbsd-compat/openssl-compat.h @@ -45,9 +45,6 @@ void ssh_libcrypto_init(void); #ifndef OPENSSL_RSA_MAX_MODULUS_BITS # define OPENSSL_RSA_MAX_MODULUS_BITS 16384 #endif -#ifndef OPENSSL_DSA_MAX_MODULUS_BITS -# define OPENSSL_DSA_MAX_MODULUS_BITS 10000 -#endif #ifdef LIBRESSL_VERSION_NUMBER # if LIBRESSL_VERSION_NUMBER < 0x3010000fL diff --git a/pathnames.h b/pathnames.h index e07395cb6f26..9e76dbba841f 100644 --- a/pathnames.h +++ b/pathnames.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pathnames.h,v 1.34 2025/05/05 02:48:06 djm Exp $ */ +/* $OpenBSD: pathnames.h,v 1.35 2025/05/06 05:40:56 djm Exp $ */ /* * Author: Tatu Ylonen @@ -36,7 +36,6 @@ */ #define _PATH_SERVER_CONFIG_FILE SSHDIR "/sshd_config" #define _PATH_HOST_CONFIG_FILE SSHDIR "/ssh_config" -#define _PATH_HOST_DSA_KEY_FILE SSHDIR "/ssh_host_dsa_key" #define _PATH_HOST_ECDSA_KEY_FILE SSHDIR "/ssh_host_ecdsa_key" #define _PATH_HOST_ED25519_KEY_FILE SSHDIR "/ssh_host_ed25519_key" #define _PATH_HOST_XMSS_KEY_FILE SSHDIR "/ssh_host_xmss_key" @@ -87,7 +86,6 @@ * Name of the default file containing client-side authentication key. This * file should only be readable by the user him/herself. */ -#define _PATH_SSH_CLIENT_ID_DSA _PATH_SSH_USER_DIR "/id_dsa" #define _PATH_SSH_CLIENT_ID_ECDSA _PATH_SSH_USER_DIR "/id_ecdsa" #define _PATH_SSH_CLIENT_ID_RSA _PATH_SSH_USER_DIR "/id_rsa" #define _PATH_SSH_CLIENT_ID_ED25519 _PATH_SSH_USER_DIR "/id_ed25519" diff --git a/readconf.c b/readconf.c index 7cbe7d2c2dc5..a56cbe0da7d0 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.398 2025/03/18 04:53:14 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.399 2025/05/06 05:40:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2869,9 +2869,6 @@ fill_default_options(Options * options) add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ED25519_SK, 0); add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_XMSS, 0); -#ifdef WITH_DSA - add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_DSA, 0); -#endif } if (options->escape_char == -1) options->escape_char = '~'; diff --git a/ssh-add.c b/ssh-add.c index 0035cb84a0c1..18897a0a834d 100644 --- a/ssh-add.c +++ b/ssh-add.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-add.c,v 1.173 2024/09/06 02:30:44 djm Exp $ */ +/* $OpenBSD: ssh-add.c,v 1.174 2025/05/06 05:40:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -85,9 +85,6 @@ static char *default_files[] = { _PATH_SSH_CLIENT_ID_ED25519, _PATH_SSH_CLIENT_ID_ED25519_SK, _PATH_SSH_CLIENT_ID_XMSS, -#ifdef WITH_DSA - _PATH_SSH_CLIENT_ID_DSA, -#endif NULL }; diff --git a/ssh-dss.c b/ssh-dss.c deleted file mode 100644 index aea661377f5c..000000000000 --- a/ssh-dss.c +++ /dev/null @@ -1,457 +0,0 @@ -/* $OpenBSD: ssh-dss.c,v 1.50 2024/01/11 01:45:36 djm Exp $ */ -/* - * Copyright (c) 2000 Markus Friedl. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "includes.h" - -#if defined(WITH_OPENSSL) && defined(WITH_DSA) - -#include - -#include -#include -#include - -#include -#include - -#include "sshbuf.h" -#include "ssherr.h" -#include "digest.h" -#define SSHKEY_INTERNAL -#include "sshkey.h" - -#include "openbsd-compat/openssl-compat.h" - -#define INTBLOB_LEN 20 -#define SIGBLOB_LEN (2*INTBLOB_LEN) - -static u_int -ssh_dss_size(const struct sshkey *key) -{ - const BIGNUM *dsa_p; - - if (key->dsa == NULL) - return 0; - DSA_get0_pqg(key->dsa, &dsa_p, NULL, NULL); - return BN_num_bits(dsa_p); -} - -static int -ssh_dss_alloc(struct sshkey *k) -{ - if ((k->dsa = DSA_new()) == NULL) - return SSH_ERR_ALLOC_FAIL; - return 0; -} - -static void -ssh_dss_cleanup(struct sshkey *k) -{ - DSA_free(k->dsa); - k->dsa = NULL; -} - -static int -ssh_dss_equal(const struct sshkey *a, const struct sshkey *b) -{ - const BIGNUM *dsa_p_a, *dsa_q_a, *dsa_g_a, *dsa_pub_key_a; - const BIGNUM *dsa_p_b, *dsa_q_b, *dsa_g_b, *dsa_pub_key_b; - - if (a->dsa == NULL || b->dsa == NULL) - return 0; - DSA_get0_pqg(a->dsa, &dsa_p_a, &dsa_q_a, &dsa_g_a); - DSA_get0_pqg(b->dsa, &dsa_p_b, &dsa_q_b, &dsa_g_b); - DSA_get0_key(a->dsa, &dsa_pub_key_a, NULL); - DSA_get0_key(b->dsa, &dsa_pub_key_b, NULL); - if (dsa_p_a == NULL || dsa_p_b == NULL || - dsa_q_a == NULL || dsa_q_b == NULL || - dsa_g_a == NULL || dsa_g_b == NULL || - dsa_pub_key_a == NULL || dsa_pub_key_b == NULL) - return 0; - if (BN_cmp(dsa_p_a, dsa_p_b) != 0) - return 0; - if (BN_cmp(dsa_q_a, dsa_q_b) != 0) - return 0; - if (BN_cmp(dsa_g_a, dsa_g_b) != 0) - return 0; - if (BN_cmp(dsa_pub_key_a, dsa_pub_key_b) != 0) - return 0; - return 1; -} - -static int -ssh_dss_serialize_public(const struct sshkey *key, struct sshbuf *b, - enum sshkey_serialize_rep opts) -{ - int r; - const BIGNUM *dsa_p, *dsa_q, *dsa_g, *dsa_pub_key; - - if (key->dsa == NULL) - return SSH_ERR_INVALID_ARGUMENT; - DSA_get0_pqg(key->dsa, &dsa_p, &dsa_q, &dsa_g); - DSA_get0_key(key->dsa, &dsa_pub_key, NULL); - if (dsa_p == NULL || dsa_q == NULL || - dsa_g == NULL || dsa_pub_key == NULL) - return SSH_ERR_INTERNAL_ERROR; - if ((r = sshbuf_put_bignum2(b, dsa_p)) != 0 || - (r = sshbuf_put_bignum2(b, dsa_q)) != 0 || - (r = sshbuf_put_bignum2(b, dsa_g)) != 0 || - (r = sshbuf_put_bignum2(b, dsa_pub_key)) != 0) - return r; - - return 0; -} - -static int -ssh_dss_serialize_private(const struct sshkey *key, struct sshbuf *b, - enum sshkey_serialize_rep opts) -{ - int r; - const BIGNUM *dsa_priv_key; - - DSA_get0_key(key->dsa, NULL, &dsa_priv_key); - if (!sshkey_is_cert(key)) { - if ((r = ssh_dss_serialize_public(key, b, opts)) != 0) - return r; - } - if ((r = sshbuf_put_bignum2(b, dsa_priv_key)) != 0) - return r; - - return 0; -} - -static int -ssh_dss_generate(struct sshkey *k, int bits) -{ - DSA *private; - - if (bits != 1024) - return SSH_ERR_KEY_LENGTH; - if ((private = DSA_new()) == NULL) - return SSH_ERR_ALLOC_FAIL; - if (!DSA_generate_parameters_ex(private, bits, NULL, 0, NULL, - NULL, NULL) || !DSA_generate_key(private)) { - DSA_free(private); - return SSH_ERR_LIBCRYPTO_ERROR; - } - k->dsa = private; - return 0; -} - -static int -ssh_dss_copy_public(const struct sshkey *from, struct sshkey *to) -{ - const BIGNUM *dsa_p, *dsa_q, *dsa_g, *dsa_pub_key; - BIGNUM *dsa_p_dup = NULL, *dsa_q_dup = NULL, *dsa_g_dup = NULL; - BIGNUM *dsa_pub_key_dup = NULL; - int r = SSH_ERR_INTERNAL_ERROR; - - DSA_get0_pqg(from->dsa, &dsa_p, &dsa_q, &dsa_g); - DSA_get0_key(from->dsa, &dsa_pub_key, NULL); - if ((dsa_p_dup = BN_dup(dsa_p)) == NULL || - (dsa_q_dup = BN_dup(dsa_q)) == NULL || - (dsa_g_dup = BN_dup(dsa_g)) == NULL || - (dsa_pub_key_dup = BN_dup(dsa_pub_key)) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - if (!DSA_set0_pqg(to->dsa, dsa_p_dup, dsa_q_dup, dsa_g_dup)) { - r = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - dsa_p_dup = dsa_q_dup = dsa_g_dup = NULL; /* transferred */ - if (!DSA_set0_key(to->dsa, dsa_pub_key_dup, NULL)) { - r = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - dsa_pub_key_dup = NULL; /* transferred */ - /* success */ - r = 0; - out: - BN_clear_free(dsa_p_dup); - BN_clear_free(dsa_q_dup); - BN_clear_free(dsa_g_dup); - BN_clear_free(dsa_pub_key_dup); - return r; -} - -static int -ssh_dss_deserialize_public(const char *ktype, struct sshbuf *b, - struct sshkey *key) -{ - int ret = SSH_ERR_INTERNAL_ERROR; - BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL, *dsa_pub_key = NULL; - - if (sshbuf_get_bignum2(b, &dsa_p) != 0 || - sshbuf_get_bignum2(b, &dsa_q) != 0 || - sshbuf_get_bignum2(b, &dsa_g) != 0 || - sshbuf_get_bignum2(b, &dsa_pub_key) != 0) { - ret = SSH_ERR_INVALID_FORMAT; - goto out; - } - if (!DSA_set0_pqg(key->dsa, dsa_p, dsa_q, dsa_g)) { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - dsa_p = dsa_q = dsa_g = NULL; /* transferred */ - if (!DSA_set0_key(key->dsa, dsa_pub_key, NULL)) { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - dsa_pub_key = NULL; /* transferred */ -#ifdef DEBUG_PK - DSA_print_fp(stderr, key->dsa, 8); -#endif - /* success */ - ret = 0; - out: - BN_clear_free(dsa_p); - BN_clear_free(dsa_q); - BN_clear_free(dsa_g); - BN_clear_free(dsa_pub_key); - return ret; -} - -static int -ssh_dss_deserialize_private(const char *ktype, struct sshbuf *b, - struct sshkey *key) -{ - int r; - BIGNUM *dsa_priv_key = NULL; - - if (!sshkey_is_cert(key)) { - if ((r = ssh_dss_deserialize_public(ktype, b, key)) != 0) - return r; - } - - if ((r = sshbuf_get_bignum2(b, &dsa_priv_key)) != 0) - return r; - if (!DSA_set0_key(key->dsa, NULL, dsa_priv_key)) { - BN_clear_free(dsa_priv_key); - return SSH_ERR_LIBCRYPTO_ERROR; - } - return 0; -} - -static int -ssh_dss_sign(struct sshkey *key, - u_char **sigp, size_t *lenp, - const u_char *data, size_t datalen, - const char *alg, const char *sk_provider, const char *sk_pin, u_int compat) -{ - DSA_SIG *sig = NULL; - const BIGNUM *sig_r, *sig_s; - u_char digest[SSH_DIGEST_MAX_LENGTH], sigblob[SIGBLOB_LEN]; - size_t rlen, slen, len, dlen = ssh_digest_bytes(SSH_DIGEST_SHA1); - struct sshbuf *b = NULL; - int ret = SSH_ERR_INVALID_ARGUMENT; - - if (lenp != NULL) - *lenp = 0; - if (sigp != NULL) - *sigp = NULL; - - if (key == NULL || key->dsa == NULL || - sshkey_type_plain(key->type) != KEY_DSA) - return SSH_ERR_INVALID_ARGUMENT; - if (dlen == 0) - return SSH_ERR_INTERNAL_ERROR; - - if ((ret = ssh_digest_memory(SSH_DIGEST_SHA1, data, datalen, - digest, sizeof(digest))) != 0) - goto out; - - if ((sig = DSA_do_sign(digest, dlen, key->dsa)) == NULL) { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - - DSA_SIG_get0(sig, &sig_r, &sig_s); - rlen = BN_num_bytes(sig_r); - slen = BN_num_bytes(sig_s); - if (rlen > INTBLOB_LEN || slen > INTBLOB_LEN) { - ret = SSH_ERR_INTERNAL_ERROR; - goto out; - } - explicit_bzero(sigblob, SIGBLOB_LEN); - BN_bn2bin(sig_r, sigblob + SIGBLOB_LEN - INTBLOB_LEN - rlen); - BN_bn2bin(sig_s, sigblob + SIGBLOB_LEN - slen); - - if ((b = sshbuf_new()) == NULL) { - ret = SSH_ERR_ALLOC_FAIL; - goto out; - } - if ((ret = sshbuf_put_cstring(b, "ssh-dss")) != 0 || - (ret = sshbuf_put_string(b, sigblob, SIGBLOB_LEN)) != 0) - goto out; - - len = sshbuf_len(b); - if (sigp != NULL) { - if ((*sigp = malloc(len)) == NULL) { - ret = SSH_ERR_ALLOC_FAIL; - goto out; - } - memcpy(*sigp, sshbuf_ptr(b), len); - } - if (lenp != NULL) - *lenp = len; - ret = 0; - out: - explicit_bzero(digest, sizeof(digest)); - DSA_SIG_free(sig); - sshbuf_free(b); - return ret; -} - -static int -ssh_dss_verify(const struct sshkey *key, - const u_char *sig, size_t siglen, - const u_char *data, size_t dlen, const char *alg, u_int compat, - struct sshkey_sig_details **detailsp) -{ - DSA_SIG *dsig = NULL; - BIGNUM *sig_r = NULL, *sig_s = NULL; - u_char digest[SSH_DIGEST_MAX_LENGTH], *sigblob = NULL; - size_t len, hlen = ssh_digest_bytes(SSH_DIGEST_SHA1); - int ret = SSH_ERR_INTERNAL_ERROR; - struct sshbuf *b = NULL; - char *ktype = NULL; - - if (key == NULL || key->dsa == NULL || - sshkey_type_plain(key->type) != KEY_DSA || - sig == NULL || siglen == 0) - return SSH_ERR_INVALID_ARGUMENT; - if (hlen == 0) - return SSH_ERR_INTERNAL_ERROR; - - /* fetch signature */ - if ((b = sshbuf_from(sig, siglen)) == NULL) - return SSH_ERR_ALLOC_FAIL; - if (sshbuf_get_cstring(b, &ktype, NULL) != 0 || - sshbuf_get_string(b, &sigblob, &len) != 0) { - ret = SSH_ERR_INVALID_FORMAT; - goto out; - } - if (strcmp("ssh-dss", ktype) != 0) { - ret = SSH_ERR_KEY_TYPE_MISMATCH; - goto out; - } - if (sshbuf_len(b) != 0) { - ret = SSH_ERR_UNEXPECTED_TRAILING_DATA; - goto out; - } - - if (len != SIGBLOB_LEN) { - ret = SSH_ERR_INVALID_FORMAT; - goto out; - } - - /* parse signature */ - if ((dsig = DSA_SIG_new()) == NULL || - (sig_r = BN_new()) == NULL || - (sig_s = BN_new()) == NULL) { - ret = SSH_ERR_ALLOC_FAIL; - goto out; - } - if ((BN_bin2bn(sigblob, INTBLOB_LEN, sig_r) == NULL) || - (BN_bin2bn(sigblob + INTBLOB_LEN, INTBLOB_LEN, sig_s) == NULL)) { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - if (!DSA_SIG_set0(dsig, sig_r, sig_s)) { - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - sig_r = sig_s = NULL; /* transferred */ - - /* sha1 the data */ - if ((ret = ssh_digest_memory(SSH_DIGEST_SHA1, data, dlen, - digest, sizeof(digest))) != 0) - goto out; - - switch (DSA_do_verify(digest, hlen, dsig, key->dsa)) { - case 1: - ret = 0; - break; - case 0: - ret = SSH_ERR_SIGNATURE_INVALID; - goto out; - default: - ret = SSH_ERR_LIBCRYPTO_ERROR; - goto out; - } - - out: - explicit_bzero(digest, sizeof(digest)); - DSA_SIG_free(dsig); - BN_clear_free(sig_r); - BN_clear_free(sig_s); - sshbuf_free(b); - free(ktype); - if (sigblob != NULL) - freezero(sigblob, len); - return ret; -} - -static const struct sshkey_impl_funcs sshkey_dss_funcs = { - /* .size = */ ssh_dss_size, - /* .alloc = */ ssh_dss_alloc, - /* .cleanup = */ ssh_dss_cleanup, - /* .equal = */ ssh_dss_equal, - /* .ssh_serialize_public = */ ssh_dss_serialize_public, - /* .ssh_deserialize_public = */ ssh_dss_deserialize_public, - /* .ssh_serialize_private = */ ssh_dss_serialize_private, - /* .ssh_deserialize_private = */ ssh_dss_deserialize_private, - /* .generate = */ ssh_dss_generate, - /* .copy_public = */ ssh_dss_copy_public, - /* .sign = */ ssh_dss_sign, - /* .verify = */ ssh_dss_verify, -}; - -const struct sshkey_impl sshkey_dss_impl = { - /* .name = */ "ssh-dss", - /* .shortname = */ "DSA", - /* .sigalg = */ NULL, - /* .type = */ KEY_DSA, - /* .nid = */ 0, - /* .cert = */ 0, - /* .sigonly = */ 0, - /* .keybits = */ 0, - /* .funcs = */ &sshkey_dss_funcs, -}; - -const struct sshkey_impl sshkey_dsa_cert_impl = { - /* .name = */ "ssh-dss-cert-v01@openssh.com", - /* .shortname = */ "DSA-CERT", - /* .sigalg = */ NULL, - /* .type = */ KEY_DSA_CERT, - /* .nid = */ 0, - /* .cert = */ 1, - /* .sigonly = */ 0, - /* .keybits = */ 0, - /* .funcs = */ &sshkey_dss_funcs, -}; - -#endif /* WITH_OPENSSL && WITH_DSA */ diff --git a/ssh-keygen.c b/ssh-keygen.c index 89c3ed287628..3217f084d384 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keygen.c,v 1.477 2024/12/04 14:24:20 djm Exp $ */ +/* $OpenBSD: ssh-keygen.c,v 1.478 2025/05/06 05:40:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1994 Tatu Ylonen , Espoo, Finland @@ -70,18 +70,14 @@ #define DEFAULT_KEY_TYPE_NAME "ed25519" /* - * Default number of bits in the RSA, DSA and ECDSA keys. These value can be + * Default number of bits in the RSA and ECDSA keys. These value can be * overridden on the command line. * - * These values, with the exception of DSA, provide security equivalent to at - * least 128 bits of security according to NIST Special Publication 800-57: - * Recommendation for Key Management Part 1 rev 4 section 5.6.1. - * For DSA it (and FIPS-186-4 section 4.2) specifies that the only size for - * which a 160bit hash is acceptable is 1kbit, and since ssh-dss specifies only - * SHA1 we limit the DSA key size 1k bits. + * These values provide security equivalent to at least 128 bits of security + * according to NIST Special Publication 800-57: Recommendation for Key + * Management Part 1 rev 4 section 5.6.1. */ #define DEFAULT_BITS 3072 -#define DEFAULT_BITS_DSA 1024 #define DEFAULT_BITS_ECDSA 256 static int quiet = 0; @@ -185,9 +181,6 @@ type_bits_valid(int type, const char *name, u_int32_t *bitsp) int nid; switch(type) { - case KEY_DSA: - *bitsp = DEFAULT_BITS_DSA; - break; case KEY_ECDSA: if (name != NULL && (nid = sshkey_ecdsa_nid_from_name(name)) > 0) @@ -203,10 +196,6 @@ type_bits_valid(int type, const char *name, u_int32_t *bitsp) } #ifdef WITH_OPENSSL switch (type) { - case KEY_DSA: - if (*bitsp != 1024) - fatal("Invalid DSA key length: must be 1024 bits"); - break; case KEY_RSA: if (*bitsp < SSH_RSA_MINIMUM_MODULUS_SIZE) fatal("Invalid RSA key length: minimum is %d bits", @@ -262,12 +251,6 @@ ask_filename(struct passwd *pw, const char *prompt) name = _PATH_SSH_CLIENT_ID_ED25519; else { switch (sshkey_type_from_shortname(key_type_name)) { -#ifdef WITH_DSA - case KEY_DSA_CERT: - case KEY_DSA: - name = _PATH_SSH_CLIENT_ID_DSA; - break; -#endif #ifdef OPENSSL_HAS_ECC case KEY_ECDSA_CERT: case KEY_ECDSA: @@ -382,12 +365,6 @@ do_convert_to_pkcs8(struct sshkey *k) EVP_PKEY_get0_RSA(k->pkey))) fatal("PEM_write_RSA_PUBKEY failed"); break; -#ifdef WITH_DSA - case KEY_DSA: - if (!PEM_write_DSA_PUBKEY(stdout, k->dsa)) - fatal("PEM_write_DSA_PUBKEY failed"); - break; -#endif #ifdef OPENSSL_HAS_ECC case KEY_ECDSA: if (!PEM_write_EC_PUBKEY(stdout, @@ -410,12 +387,6 @@ do_convert_to_pem(struct sshkey *k) EVP_PKEY_get0_RSA(k->pkey))) fatal("PEM_write_RSAPublicKey failed"); break; -#ifdef WITH_DSA - case KEY_DSA: - if (!PEM_write_DSA_PUBKEY(stdout, k->dsa)) - fatal("PEM_write_DSA_PUBKEY failed"); - break; -#endif #ifdef OPENSSL_HAS_ECC case KEY_ECDSA: if (!PEM_write_EC_PUBKEY(stdout, @@ -491,10 +462,6 @@ do_convert_private_ssh2(struct sshbuf *b) u_int magic, i1, i2, i3, i4; size_t slen; u_long e; -#ifdef WITH_DSA - BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL; - BIGNUM *dsa_pub_key = NULL, *dsa_priv_key = NULL; -#endif BIGNUM *rsa_n = NULL, *rsa_e = NULL, *rsa_d = NULL; BIGNUM *rsa_p = NULL, *rsa_q = NULL, *rsa_iqmp = NULL; BIGNUM *rsa_dmp1 = NULL, *rsa_dmq1 = NULL; @@ -526,10 +493,6 @@ do_convert_private_ssh2(struct sshbuf *b) if (strstr(type, "rsa")) { ktype = KEY_RSA; -#ifdef WITH_DSA - } else if (strstr(type, "dsa")) { - ktype = KEY_DSA; -#endif } else { free(type); return NULL; @@ -539,27 +502,6 @@ do_convert_private_ssh2(struct sshbuf *b) free(type); switch (key->type) { -#ifdef WITH_DSA - case KEY_DSA: - if ((dsa_p = BN_new()) == NULL || - (dsa_q = BN_new()) == NULL || - (dsa_g = BN_new()) == NULL || - (dsa_pub_key = BN_new()) == NULL || - (dsa_priv_key = BN_new()) == NULL) - fatal_f("BN_new"); - buffer_get_bignum_bits(b, dsa_p); - buffer_get_bignum_bits(b, dsa_g); - buffer_get_bignum_bits(b, dsa_q); - buffer_get_bignum_bits(b, dsa_pub_key); - buffer_get_bignum_bits(b, dsa_priv_key); - if (!DSA_set0_pqg(key->dsa, dsa_p, dsa_q, dsa_g)) - fatal_f("DSA_set0_pqg failed"); - dsa_p = dsa_q = dsa_g = NULL; /* transferred */ - if (!DSA_set0_key(key->dsa, dsa_pub_key, dsa_priv_key)) - fatal_f("DSA_set0_key failed"); - dsa_pub_key = dsa_priv_key = NULL; /* transferred */ - break; -#endif case KEY_RSA: if ((r = sshbuf_get_u8(b, &e1)) != 0 || (e1 < 30 && (r = sshbuf_get_u8(b, &e2)) != 0) || @@ -734,14 +676,6 @@ do_convert_from_pkcs8(struct sshkey **k, int *private) (*k)->pkey = pubkey; pubkey = NULL; break; -#ifdef WITH_DSA - case EVP_PKEY_DSA: - if ((*k = sshkey_new(KEY_UNSPEC)) == NULL) - fatal("sshkey_new failed"); - (*k)->type = KEY_DSA; - (*k)->dsa = EVP_PKEY_get1_DSA(pubkey); - break; -#endif #ifdef OPENSSL_HAS_ECC case EVP_PKEY_EC: if ((*k = sshkey_new(KEY_UNSPEC)) == NULL) @@ -817,12 +751,6 @@ do_convert_from(struct passwd *pw) fprintf(stdout, "\n"); } else { switch (k->type) { -#ifdef WITH_DSA - case KEY_DSA: - ok = PEM_write_DSAPrivateKey(stdout, k->dsa, NULL, - NULL, 0, NULL, NULL); - break; -#endif #ifdef OPENSSL_HAS_ECC case KEY_ECDSA: ok = PEM_write_ECPrivateKey(stdout, @@ -3329,7 +3257,7 @@ usage(void) fprintf(stderr, "usage: ssh-keygen [-q] [-a rounds] [-b bits] [-C comment] [-f output_keyfile]\n" " [-m format] [-N new_passphrase] [-O option]\n" - " [-t dsa | ecdsa | ecdsa-sk | ed25519 | ed25519-sk | rsa]\n" + " [-t ecdsa | ecdsa-sk | ed25519 | ed25519-sk | rsa]\n" " [-w provider] [-Z cipher]\n" " ssh-keygen -p [-a rounds] [-f keyfile] [-m format] [-N new_passphrase]\n" " [-P old_passphrase] [-Z cipher]\n" @@ -3805,11 +3733,6 @@ main(int argc, char **argv) n += do_print_resource_record(pw, _PATH_HOST_RSA_KEY_FILE, rr_hostname, print_generic, opts, nopts); -#ifdef WITH_DSA - n += do_print_resource_record(pw, - _PATH_HOST_DSA_KEY_FILE, rr_hostname, - print_generic, opts, nopts); -#endif n += do_print_resource_record(pw, _PATH_HOST_ECDSA_KEY_FILE, rr_hostname, print_generic, opts, nopts); diff --git a/ssh-keyscan.c b/ssh-keyscan.c index 3436c0b5c7c6..7b1e0ca86500 100644 --- a/ssh-keyscan.c +++ b/ssh-keyscan.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keyscan.c,v 1.165 2024/12/06 15:17:15 djm Exp $ */ +/* $OpenBSD: ssh-keyscan.c,v 1.166 2025/05/06 05:40:56 djm Exp $ */ /* * Copyright 1995, 1996 by David Mazieres . * @@ -62,15 +62,14 @@ int IPv4or6 = AF_UNSPEC; int ssh_port = SSH_DEFAULT_PORT; -#define KT_DSA (1) -#define KT_RSA (1<<1) -#define KT_ECDSA (1<<2) -#define KT_ED25519 (1<<3) -#define KT_XMSS (1<<4) -#define KT_ECDSA_SK (1<<5) -#define KT_ED25519_SK (1<<6) +#define KT_RSA (1) +#define KT_ECDSA (1<<1) +#define KT_ED25519 (1<<2) +#define KT_XMSS (1<<3) +#define KT_ECDSA_SK (1<<4) +#define KT_ED25519_SK (1<<5) -#define KT_MIN KT_DSA +#define KT_MIN KT_RSA #define KT_MAX KT_ED25519_SK int get_cert = 0; @@ -240,10 +239,6 @@ keygrab_ssh2(con *c) int r; switch (c->c_keytype) { - case KT_DSA: - myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = get_cert ? - "ssh-dss-cert-v01@openssh.com" : "ssh-dss"; - break; case KT_RSA: myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = get_cert ? "rsa-sha2-512-cert-v01@openssh.com," @@ -743,11 +738,6 @@ main(int argc, char **argv) int type = sshkey_type_from_shortname(tname); switch (type) { -#ifdef WITH_DSA - case KEY_DSA: - get_keytypes |= KT_DSA; - break; -#endif case KEY_ECDSA: get_keytypes |= KT_ECDSA; break; diff --git a/ssh-keysign.c b/ssh-keysign.c index 955f7b0abad9..4d65dd1d33a0 100644 --- a/ssh-keysign.c +++ b/ssh-keysign.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keysign.c,v 1.75 2025/02/15 01:48:30 djm Exp $ */ +/* $OpenBSD: ssh-keysign.c,v 1.76 2025/05/06 05:40:56 djm Exp $ */ /* * Copyright (c) 2002 Markus Friedl. All rights reserved. * @@ -200,9 +200,6 @@ main(int argc, char **argv) i = 0; /* XXX This really needs to read sshd_config for the paths */ -#ifdef WITH_DSA - key_fd[i++] = open(_PATH_HOST_DSA_KEY_FILE, O_RDONLY); -#endif key_fd[i++] = open(_PATH_HOST_ECDSA_KEY_FILE, O_RDONLY); key_fd[i++] = open(_PATH_HOST_ED25519_KEY_FILE, O_RDONLY); key_fd[i++] = open(_PATH_HOST_XMSS_KEY_FILE, O_RDONLY); diff --git a/ssh.c b/ssh.c index dc4886d0ecdf..b2172b810a61 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.612 2025/04/09 01:24:40 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.613 2025/05/06 05:40:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1746,15 +1746,9 @@ main(int ac, char **av) L_CERT(_PATH_HOST_ECDSA_KEY_FILE, 0); L_CERT(_PATH_HOST_ED25519_KEY_FILE, 1); L_CERT(_PATH_HOST_RSA_KEY_FILE, 2); -#ifdef WITH_DSA - L_CERT(_PATH_HOST_DSA_KEY_FILE, 3); -#endif L_PUBKEY(_PATH_HOST_ECDSA_KEY_FILE, 4); L_PUBKEY(_PATH_HOST_ED25519_KEY_FILE, 5); L_PUBKEY(_PATH_HOST_RSA_KEY_FILE, 6); -#ifdef WITH_DSA - L_PUBKEY(_PATH_HOST_DSA_KEY_FILE, 7); -#endif L_CERT(_PATH_HOST_XMSS_KEY_FILE, 8); L_PUBKEY(_PATH_HOST_XMSS_KEY_FILE, 9); if (loaded == 0) diff --git a/ssh_config b/ssh_config index cc5663562e95..238a0c5e371b 100644 --- a/ssh_config +++ b/ssh_config @@ -1,4 +1,4 @@ -# $OpenBSD: ssh_config,v 1.36 2023/08/02 23:04:38 djm Exp $ +# $OpenBSD: ssh_config,v 1.37 2025/05/06 05:40:56 djm Exp $ # This is the ssh client system-wide configuration file. See # ssh_config(5) for more information. This file provides defaults for @@ -30,7 +30,6 @@ # ConnectTimeout 0 # StrictHostKeyChecking ask # IdentityFile ~/.ssh/id_rsa -# IdentityFile ~/.ssh/id_dsa # IdentityFile ~/.ssh/id_ecdsa # IdentityFile ~/.ssh/id_ed25519 # Port 22 diff --git a/sshconnect.c b/sshconnect.c index c86182d13673..b306fa3cc24f 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.369 2024/12/06 16:21:48 djm Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.370 2025/05/06 05:40:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1626,9 +1626,6 @@ show_other_keys(struct hostkeys *hostkeys, struct sshkey *key) { int type[] = { KEY_RSA, -#ifdef WITH_DSA - KEY_DSA, -#endif KEY_ECDSA, KEY_ED25519, KEY_XMSS, diff --git a/sshd-auth.c b/sshd-auth.c index 30eecd8a4723..5de06a5baa49 100644 --- a/sshd-auth.c +++ b/sshd-auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd-auth.c,v 1.3 2025/01/16 06:37:10 dtucker Exp $ */ +/* $OpenBSD: sshd-auth.c,v 1.4 2025/05/06 05:40:56 djm Exp $ */ /* * SSH2 implementation: * Privilege Separation: @@ -250,7 +250,6 @@ list_hostkey_types(void) append_hostkey_type(b, "rsa-sha2-512"); append_hostkey_type(b, "rsa-sha2-256"); /* FALLTHROUGH */ - case KEY_DSA: case KEY_ECDSA: case KEY_ED25519: case KEY_ECDSA_SK: @@ -271,7 +270,6 @@ list_hostkey_types(void) append_hostkey_type(b, "rsa-sha2-256-cert-v01@openssh.com"); /* FALLTHROUGH */ - case KEY_DSA_CERT: case KEY_ECDSA_CERT: case KEY_ED25519_CERT: case KEY_ECDSA_SK_CERT: @@ -297,7 +295,6 @@ get_hostkey_public_by_type(int type, int nid, struct ssh *ssh) for (i = 0; i < options.num_host_key_files; i++) { switch (type) { case KEY_RSA_CERT: - case KEY_DSA_CERT: case KEY_ECDSA_CERT: case KEY_ED25519_CERT: case KEY_ECDSA_SK_CERT: diff --git a/sshd-session.c b/sshd-session.c index c64eb29fcefd..60f887e92d7b 100644 --- a/sshd-session.c +++ b/sshd-session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd-session.c,v 1.12 2025/03/12 22:43:44 djm Exp $ */ +/* $OpenBSD: sshd-session.c,v 1.13 2025/05/06 05:40:56 djm Exp $ */ /* * SSH2 implementation: * Privilege Separation: @@ -485,7 +485,6 @@ get_hostkey_by_type(int type, int nid, int need_private, struct ssh *ssh) for (i = 0; i < options.num_host_key_files; i++) { switch (type) { case KEY_RSA_CERT: - case KEY_DSA_CERT: case KEY_ECDSA_CERT: case KEY_ED25519_CERT: case KEY_ECDSA_SK_CERT: diff --git a/sshd.c b/sshd.c index 4a93e29e4c04..03732bb09c50 100644 --- a/sshd.c +++ b/sshd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.617 2025/04/07 08:12:22 dtucker Exp $ */ +/* $OpenBSD: sshd.c,v 1.618 2025/05/06 05:40:56 djm Exp $ */ /* * Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved. * Copyright (c) 2002 Niels Provos. All rights reserved. @@ -1658,7 +1658,6 @@ main(int ac, char **av) switch (keytype) { case KEY_RSA: - case KEY_DSA: case KEY_ECDSA: case KEY_ED25519: case KEY_ECDSA_SK: diff --git a/sshkey.c b/sshkey.c index ab80752b8d0b..55a156818962 100644 --- a/sshkey.c +++ b/sshkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshkey.c,v 1.148 2024/12/03 15:53:51 tb Exp $ */ +/* $OpenBSD: sshkey.c,v 1.149 2025/05/06 05:40:56 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * Copyright (c) 2008 Alexander von Gernler. All rights reserved. @@ -119,10 +119,6 @@ extern const struct sshkey_impl sshkey_rsa_sha256_impl; extern const struct sshkey_impl sshkey_rsa_sha256_cert_impl; extern const struct sshkey_impl sshkey_rsa_sha512_impl; extern const struct sshkey_impl sshkey_rsa_sha512_cert_impl; -# ifdef WITH_DSA -extern const struct sshkey_impl sshkey_dss_impl; -extern const struct sshkey_impl sshkey_dsa_cert_impl; -# endif #endif /* WITH_OPENSSL */ #ifdef WITH_XMSS extern const struct sshkey_impl sshkey_xmss_impl; @@ -152,10 +148,6 @@ const struct sshkey_impl * const keyimpls[] = { &sshkey_ecdsa_sk_webauthn_impl, # endif /* ENABLE_SK */ # endif /* OPENSSL_HAS_ECC */ -# ifdef WITH_DSA - &sshkey_dss_impl, - &sshkey_dsa_cert_impl, -# endif &sshkey_rsa_impl, &sshkey_rsa_cert_impl, &sshkey_rsa_sha256_impl, @@ -451,8 +443,6 @@ sshkey_type_plain(int type) switch (type) { case KEY_RSA_CERT: return KEY_RSA; - case KEY_DSA_CERT: - return KEY_DSA; case KEY_ECDSA_CERT: return KEY_ECDSA; case KEY_ECDSA_SK_CERT: @@ -475,8 +465,6 @@ sshkey_type_certified(int type) switch (type) { case KEY_RSA: return KEY_RSA_CERT; - case KEY_DSA: - return KEY_DSA_CERT; case KEY_ECDSA: return KEY_ECDSA_CERT; case KEY_ECDSA_SK: @@ -3322,20 +3310,6 @@ sshkey_private_to_blob_pem_pkcs8(struct sshkey *key, struct sshbuf *buf, goto out; switch (key->type) { -#ifdef WITH_DSA - case KEY_DSA: - if (format == SSHKEY_PRIVATE_PEM) { - success = PEM_write_bio_DSAPrivateKey(bio, key->dsa, - cipher, passphrase, len, NULL, NULL); - } else { - if ((pkey = EVP_PKEY_new()) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - success = EVP_PKEY_set1_DSA(pkey, key->dsa); - } - break; -#endif #ifdef OPENSSL_HAS_ECC case KEY_ECDSA: if (format == SSHKEY_PRIVATE_PEM) { @@ -3403,7 +3377,6 @@ sshkey_private_to_fileblob(struct sshkey *key, struct sshbuf *blob, { switch (key->type) { #ifdef WITH_OPENSSL - case KEY_DSA: case KEY_ECDSA: case KEY_RSA: break; /* see below */ @@ -3578,19 +3551,6 @@ sshkey_parse_private_pem_fileblob(struct sshbuf *blob, int type, prv->pkey = pk; if ((r = sshkey_check_rsa_length(prv, 0)) != 0) goto out; -#ifdef WITH_DSA - } else if (EVP_PKEY_base_id(pk) == EVP_PKEY_DSA && - (type == KEY_UNSPEC || type == KEY_DSA)) { - if ((prv = sshkey_new(KEY_UNSPEC)) == NULL) { - r = SSH_ERR_ALLOC_FAIL; - goto out; - } - prv->dsa = EVP_PKEY_get1_DSA(pk); - prv->type = KEY_DSA; -#ifdef DEBUG_PK - DSA_print_fp(stderr, prv->dsa, 8); -#endif -#endif #ifdef OPENSSL_HAS_ECC } else if (EVP_PKEY_base_id(pk) == EVP_PKEY_EC && (type == KEY_UNSPEC || type == KEY_ECDSA)) { diff --git a/sshkey.h b/sshkey.h index 19bbbac7dc0f..5fa410b9431d 100644 --- a/sshkey.h +++ b/sshkey.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sshkey.h,v 1.66 2025/04/02 04:28:03 tb Exp $ */ +/* $OpenBSD: sshkey.h,v 1.67 2025/05/06 05:40:56 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -30,9 +30,6 @@ #ifdef WITH_OPENSSL #include -#ifdef WITH_DSA -#include -#endif #include # ifdef OPENSSL_HAS_ECC # include @@ -46,7 +43,6 @@ #else /* WITH_OPENSSL */ # define BIGNUM void # define RSA void -# define DSA void # define EC_KEY void # define EC_GROUP void # define EC_POINT void @@ -62,11 +58,9 @@ struct sshbuf; /* Key types */ enum sshkey_types { KEY_RSA, - KEY_DSA, KEY_ECDSA, KEY_ED25519, KEY_RSA_CERT, - KEY_DSA_CERT, KEY_ECDSA_CERT, KEY_ED25519_CERT, KEY_XMSS, @@ -129,8 +123,6 @@ struct sshkey_cert { struct sshkey { int type; int flags; - /* KEY_DSA */ - DSA *dsa; /* KEY_ECDSA and KEY_ECDSA_SK */ int ecdsa_nid; /* NID of curve */ /* libcrypto-backed keys */ @@ -353,7 +345,6 @@ int check_rsa_length(const RSA *rsa); /* XXX remove */ #if !defined(WITH_OPENSSL) # undef RSA -# undef DSA # undef EC_KEY # undef EC_GROUP # undef EC_POINT From 55b38ff4d7286c8fac2a472da664462e0f2d75e0 Mon Sep 17 00:00:00 2001 From: "deraadt@openbsd.org" Date: Tue, 6 May 2025 15:15:05 +0000 Subject: [PATCH 27/68] upstream: test ssh-agent with the -T flag to force the old /tmp location rather than inside the homedir. During relink operation, /.ssh/agent was created which is surprising. This test sequence could use some improvement so this is a temporary fix. observed by florian, change ok semarie OpenBSD-Commit-ID: c7246a6b519ac390ca550719f91acfdaef1fa0f0 --- .skipped-commit-ids | 1 + 1 file changed, 1 insertion(+) diff --git a/.skipped-commit-ids b/.skipped-commit-ids index 7988e25006f4..319beea0dee6 100644 --- a/.skipped-commit-ids +++ b/.skipped-commit-ids @@ -39,6 +39,7 @@ fb39324748824cb0387e9d67c41d1bef945c54ea Makefile change 112aacedd3b61cc5c34b1fa6d9fb759214179172 Makefile change a959fc45ea3431b36f52eda04faefc58bcde00db groupaccess.c changes 6d07e4606997e36b860621a14dd41975f2902f8f Makefile.inc +c7246a6b519ac390ca550719f91acfdaef1fa0f0 Makefile relink change Old upstream tree: From 93e904a673a632604525fdc98b940b7996f1ce54 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Wed, 7 May 2025 04:10:21 +0000 Subject: [PATCH 28/68] upstream: memory leak on error path; bz3821 OpenBSD-Commit-ID: 65577596a15ad6dd9a1ab3fc24c1c31303ee6e2b --- misc-agent.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/misc-agent.c b/misc-agent.c index f13ccdf26b5d..9d96880e9090 100644 --- a/misc-agent.c +++ b/misc-agent.c @@ -266,10 +266,10 @@ socket_is_stale(const char *path) void agent_cleanup_stale(const char *homedir, int ignore_hosthash) { - DIR *d; + DIR *d = NULL; struct dirent *dp; struct stat sb; - char *prefix = NULL, *dirpath, *path; + char *prefix = NULL, *dirpath = NULL, *path; struct timespec now, sub, *mtimp = NULL; /* Only consider sockets last modified > 1 hour ago */ @@ -295,8 +295,7 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash) if ((d = opendir(dirpath)) == NULL) { if (errno != ENOENT) error_f("opendir \"%s\": %s", dirpath, strerror(errno)); - free(dirpath); - return; + goto out; } while ((dp = readdir(d)) != NULL) { if (dp->d_type != DT_SOCK && dp->d_type != DT_UNKNOWN) @@ -334,8 +333,9 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash) } free(path); } - closedir(d); + out: + if (d != NULL) + closedir(d); free(dirpath); free(prefix); } - From c5dbbe8805caaee132545ab4cffd3b2221e80975 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Tue, 15 Apr 2025 05:00:13 +0000 Subject: [PATCH 29/68] upstream: missing ifdef OpenBSD-Regress-ID: 7260fb672de5738c17dec06c71a5be0186bb2b09 --- regress/unittests/sshkey/test_sshkey.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regress/unittests/sshkey/test_sshkey.c b/regress/unittests/sshkey/test_sshkey.c index fc744eb7430f..aca1f2406d50 100644 --- a/regress/unittests/sshkey/test_sshkey.c +++ b/regress/unittests/sshkey/test_sshkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_sshkey.c,v 1.26 2025/04/15 04:00:42 djm Exp $ */ +/* $OpenBSD: test_sshkey.c,v 1.27 2025/04/15 05:00:13 djm Exp $ */ /* * Regress test for sshkey.h key management API * From 0404fa799746c283325a463c363436eb152daefc Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Tue, 15 Apr 2025 05:31:24 +0000 Subject: [PATCH 30/68] upstream: another missing ifdef OpenBSD-Regress-ID: 4f71f8f122eac4cbf7f1d2088a9be45317dd3e4a --- regress/unittests/sshkey/test_sshkey.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regress/unittests/sshkey/test_sshkey.c b/regress/unittests/sshkey/test_sshkey.c index aca1f2406d50..53bdc0ca62d8 100644 --- a/regress/unittests/sshkey/test_sshkey.c +++ b/regress/unittests/sshkey/test_sshkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_sshkey.c,v 1.27 2025/04/15 05:00:13 djm Exp $ */ +/* $OpenBSD: test_sshkey.c,v 1.28 2025/04/15 05:31:24 djm Exp $ */ /* * Regress test for sshkey.h key management API * From 7cc8e150d51a4545b86d996692b541419b35d1a3 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Tue, 6 May 2025 06:05:48 +0000 Subject: [PATCH 31/68] upstream: remove DSA from the regression/unit test suite too. OpenBSD-Regress-ID: 4424d2eaf0bce3887318ef6d18de6c06f3617d6e --- INSTALL | 2 +- Makefile.in | 2 - contrib/redhat/openssh.spec | 14 -- contrib/redhat/sshd.init | 2 +- openbsd-compat/openssl-compat.h | 1 - regress/Makefile | 30 +-- regress/agent.sh | 8 +- regress/cert-hostkey.sh | 4 +- regress/cert-userkey.sh | 6 +- regress/dsa_ssh2.prv | 14 -- regress/dsa_ssh2.pub | 13 -- regress/hostbased.sh | 3 +- regress/keytype.sh | 4 +- regress/knownhosts-command.sh | 3 +- regress/krl.sh | 3 +- regress/limit-keytype.sh | 6 +- regress/misc/fuzz-harness/fixed-keys.h | 35 --- .../testdata/create-agent-corpus.sh | 2 +- .../fuzz-harness/testdata/id_dsa-cert.pub | 1 - regress/misc/ssh-verify-attestation/Makefile | 4 +- regress/ssh-com-client.sh | 14 +- regress/ssh-com.sh | 26 +-- regress/ssh2putty.sh | 3 +- regress/sshcfgparse.sh | 27 +-- regress/unittests/Makefile.inc | 6 +- regress/unittests/authopt/Makefile | 4 +- regress/unittests/hostkeys/Makefile | 4 +- regress/unittests/hostkeys/mktestdata.sh | 13 +- regress/unittests/hostkeys/test_iterate.c | 212 ++++-------------- regress/unittests/hostkeys/testdata/dsa_1.pub | 1 - regress/unittests/hostkeys/testdata/dsa_2.pub | 1 - regress/unittests/hostkeys/testdata/dsa_3.pub | 1 - regress/unittests/hostkeys/testdata/dsa_4.pub | 1 - regress/unittests/hostkeys/testdata/dsa_5.pub | 1 - regress/unittests/hostkeys/testdata/dsa_6.pub | 1 - .../unittests/hostkeys/testdata/known_hosts | 12 +- regress/unittests/kex/Makefile | 4 +- regress/unittests/kex/test_kex.c | 5 +- regress/unittests/sshkey/Makefile | 4 +- regress/unittests/sshkey/common.c | 37 +-- regress/unittests/sshkey/common.h | 5 +- regress/unittests/sshkey/mktestdata.sh | 53 +---- regress/unittests/sshkey/test_file.c | 96 +------- regress/unittests/sshkey/test_fuzz.c | 75 +------ regress/unittests/sshkey/test_sshkey.c | 69 +----- regress/unittests/sshkey/testdata/dsa_1 | 12 - .../unittests/sshkey/testdata/dsa_1-cert.fp | 1 - .../unittests/sshkey/testdata/dsa_1-cert.pub | 1 - regress/unittests/sshkey/testdata/dsa_1.fp | 1 - regress/unittests/sshkey/testdata/dsa_1.fp.bb | 1 - .../unittests/sshkey/testdata/dsa_1.param.g | 1 - .../sshkey/testdata/dsa_1.param.priv | 1 - .../unittests/sshkey/testdata/dsa_1.param.pub | 1 - regress/unittests/sshkey/testdata/dsa_1.pub | 1 - regress/unittests/sshkey/testdata/dsa_1_pw | 15 -- regress/unittests/sshkey/testdata/dsa_2 | 12 - regress/unittests/sshkey/testdata/dsa_2.fp | 1 - regress/unittests/sshkey/testdata/dsa_2.fp.bb | 1 - regress/unittests/sshkey/testdata/dsa_2.pub | 1 - regress/unittests/sshkey/testdata/dsa_n | 21 -- regress/unittests/sshkey/testdata/dsa_n_pw | 21 -- regress/unittests/sshsig/Makefile | 4 +- regress/unittests/sshsig/mktestdata.sh | 8 +- regress/unittests/sshsig/testdata/dsa | 12 - regress/unittests/sshsig/testdata/dsa.pub | 1 - regress/unittests/sshsig/testdata/dsa.sig | 13 -- regress/unittests/sshsig/tests.c | 7 +- 67 files changed, 126 insertions(+), 843 deletions(-) delete mode 100644 regress/dsa_ssh2.prv delete mode 100644 regress/dsa_ssh2.pub delete mode 100644 regress/misc/fuzz-harness/testdata/id_dsa-cert.pub delete mode 100644 regress/unittests/hostkeys/testdata/dsa_1.pub delete mode 100644 regress/unittests/hostkeys/testdata/dsa_2.pub delete mode 100644 regress/unittests/hostkeys/testdata/dsa_3.pub delete mode 100644 regress/unittests/hostkeys/testdata/dsa_4.pub delete mode 100644 regress/unittests/hostkeys/testdata/dsa_5.pub delete mode 100644 regress/unittests/hostkeys/testdata/dsa_6.pub delete mode 100644 regress/unittests/sshkey/testdata/dsa_1 delete mode 100644 regress/unittests/sshkey/testdata/dsa_1-cert.fp delete mode 100644 regress/unittests/sshkey/testdata/dsa_1-cert.pub delete mode 100644 regress/unittests/sshkey/testdata/dsa_1.fp delete mode 100644 regress/unittests/sshkey/testdata/dsa_1.fp.bb delete mode 100644 regress/unittests/sshkey/testdata/dsa_1.param.g delete mode 100644 regress/unittests/sshkey/testdata/dsa_1.param.priv delete mode 100644 regress/unittests/sshkey/testdata/dsa_1.param.pub delete mode 100644 regress/unittests/sshkey/testdata/dsa_1.pub delete mode 100644 regress/unittests/sshkey/testdata/dsa_1_pw delete mode 100644 regress/unittests/sshkey/testdata/dsa_2 delete mode 100644 regress/unittests/sshkey/testdata/dsa_2.fp delete mode 100644 regress/unittests/sshkey/testdata/dsa_2.fp.bb delete mode 100644 regress/unittests/sshkey/testdata/dsa_2.pub delete mode 100644 regress/unittests/sshkey/testdata/dsa_n delete mode 100644 regress/unittests/sshkey/testdata/dsa_n_pw delete mode 100644 regress/unittests/sshsig/testdata/dsa delete mode 100644 regress/unittests/sshsig/testdata/dsa.pub delete mode 100644 regress/unittests/sshsig/testdata/dsa.sig diff --git a/INSTALL b/INSTALL index 3ad1659f36f6..56e351af60e1 100644 --- a/INSTALL +++ b/INSTALL @@ -245,7 +245,7 @@ manually using the following commands: ssh-keygen -t [type] -f /etc/ssh/ssh_host_key -N "" -for each of the types you wish to generate (rsa, dsa or ecdsa) or +for each of the types you wish to generate (rsa, ed25519 or ecdsa) or ssh-keygen -A diff --git a/Makefile.in b/Makefile.in index ae86a712b17b..672bf4493cf8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -194,7 +194,6 @@ PATHSUBS = \ -e 's|/etc/shosts.equiv|$(sysconfdir)/shosts.equiv|g' \ -e 's|/etc/ssh/ssh_host_key|$(sysconfdir)/ssh_host_key|g' \ -e 's|/etc/ssh/ssh_host_ecdsa_key|$(sysconfdir)/ssh_host_ecdsa_key|g' \ - -e 's|/etc/ssh/ssh_host_dsa_key|$(sysconfdir)/ssh_host_dsa_key|g' \ -e 's|/etc/ssh/ssh_host_rsa_key|$(sysconfdir)/ssh_host_rsa_key|g' \ -e 's|/etc/ssh/ssh_host_ed25519_key|$(sysconfdir)/ssh_host_ed25519_key|g' \ -e 's|/var/run/sshd.pid|$(piddir)/sshd.pid|g' \ @@ -494,7 +493,6 @@ host-key: ssh-keygen$(EXEEXT) fi host-key-force: ssh-keygen$(EXEEXT) ssh$(EXEEXT) - ./ssh-keygen -t dsa -f $(DESTDIR)$(sysconfdir)/ssh_host_dsa_key -N "" ./ssh-keygen -t rsa -f $(DESTDIR)$(sysconfdir)/ssh_host_rsa_key -N "" ./ssh-keygen -t ed25519 -f $(DESTDIR)$(sysconfdir)/ssh_host_ed25519_key -N "" if ./ssh -Q key | grep ecdsa >/dev/null ; then \ diff --git a/contrib/redhat/openssh.spec b/contrib/redhat/openssh.spec index 74116b485135..b60695f09f95 100644 --- a/contrib/redhat/openssh.spec +++ b/contrib/redhat/openssh.spec @@ -281,20 +281,6 @@ if [ "$1" != 0 -a -r /var/run/sshd.pid ] ; then touch /var/run/sshd.restart fi -%triggerun server -- openssh-server < 2.5.0p1 -# Count the number of HostKey and HostDsaKey statements we have. -gawk 'BEGIN {IGNORECASE=1} - /^hostkey/ || /^hostdsakey/ {sawhostkey = sawhostkey + 1} - END {exit sawhostkey}' /etc/ssh/sshd_config -# And if we only found one, we know the client was relying on the old default -# behavior, which loaded the the SSH2 DSA host key when HostDsaKey wasn't -# specified. Now that HostKey is used for both SSH1 and SSH2 keys, specifying -# one nullifies the default, which would have loaded both. -if [ $? -eq 1 ] ; then - echo HostKey /etc/ssh/ssh_host_rsa_key >> /etc/ssh/sshd_config - echo HostKey /etc/ssh/ssh_host_dsa_key >> /etc/ssh/sshd_config -fi - %triggerpostun server -- ssh-server if [ "$1" != 0 ] ; then /sbin/chkconfig --add sshd diff --git a/contrib/redhat/sshd.init b/contrib/redhat/sshd.init index 8ee5fcd3bb4f..b82545956ac8 100755 --- a/contrib/redhat/sshd.init +++ b/contrib/redhat/sshd.init @@ -41,7 +41,7 @@ start() /usr/bin/ssh-keygen -A if [ -x /sbin/restorecon ]; then /sbin/restorecon /etc/ssh/ssh_host_rsa_key.pub - /sbin/restorecon /etc/ssh/ssh_host_dsa_key.pub + /sbin/restorecon /etc/ssh/ssh_host_ed25519_key.pub /sbin/restorecon /etc/ssh/ssh_host_ecdsa_key.pub fi diff --git a/openbsd-compat/openssl-compat.h b/openbsd-compat/openssl-compat.h index a7c41c235b5c..936f4068d843 100644 --- a/openbsd-compat/openssl-compat.h +++ b/openbsd-compat/openssl-compat.h @@ -24,7 +24,6 @@ #include #include #include -#include #ifdef OPENSSL_HAS_ECC #include #endif diff --git a/regress/Makefile b/regress/Makefile index 8b69e14e998f..d97ea34a2050 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -2,7 +2,7 @@ tests: prep file-tests t-exec unit -REGRESS_TARGETS= t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 +REGRESS_TARGETS= t1 t2 t3 t4 t5 t7 t9 t10 t11 t12 # File based tests file-tests: $(REGRESS_TARGETS) @@ -130,9 +130,9 @@ CLEANFILES= *.core actual agent-key.* authorized_keys_${USERNAME} \ ed25519-agent.pub ed25519 ed25519.pub empty.in \ expect failed-regress.log failed-ssh.log failed-sshd.log \ hkr.* host.ecdsa-sha2-nistp256 host.ecdsa-sha2-nistp384 \ - host.ecdsa-sha2-nistp521 host.ssh-dss host.ssh-ed25519 \ + host.ecdsa-sha2-nistp521 host.ssh-ed25519 \ host.ssh-rsa host_ca_key* host_krl_* host_revoked_* key.* \ - key.dsa-* key.ecdsa-* key.ed25519-512 \ + key.ecdsa-* key.ed25519-512 \ key.ed25519-512.pub key.rsa-* keys-command-args kh.* askpass \ known_hosts known_hosts-cert known_hosts.* krl-* ls.copy \ modpipe netcat no_identity_config \ @@ -191,36 +191,18 @@ t5: ${TEST_SSH_SSHKEYGEN} -Bf ${.CURDIR}/rsa_openssh.pub |\ awk '{print $$2}' | diff - ${.CURDIR}/t5.ok ; \ fi -t6: - set -xe ; if ${TEST_SSH_SSH} -Q key | grep -q "^ssh-dss" ; then \ - ${TEST_SSH_SSHKEYGEN} -if ${.CURDIR}/dsa_ssh2.prv > $(OBJ)/t6.out1 ; \ - ${TEST_SSH_SSHKEYGEN} -if ${.CURDIR}/dsa_ssh2.pub > $(OBJ)/t6.out2 ; \ - chmod 600 $(OBJ)/t6.out1 ; \ - ${TEST_SSH_SSHKEYGEN} -yf $(OBJ)/t6.out1 | diff - $(OBJ)/t6.out2 ; \ - fi $(OBJ)/t7.out: - set -xe ; if ${TEST_SSH_SSH} -Q key | grep -q "^ssh-dss" ; then \ + set -xe ; if ${TEST_SSH_SSH} -Q key | grep -q "^ssh-rsa" ; then \ ${TEST_SSH_SSHKEYGEN} -q -t rsa -N '' -f $@ ; \ fi t7: $(OBJ)/t7.out - set -xe ; if ${TEST_SSH_SSH} -Q key | grep -q "^ssh-dss" ; then \ + set -xe ; if ${TEST_SSH_SSH} -Q key | grep -q "^ssh-rsa" ; then \ ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t7.out > /dev/null ; \ ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t7.out > /dev/null ; \ fi -$(OBJ)/t8.out: - set -xe ; if ssh -Q key | grep -q "^ssh-dss" ; then \ - ${TEST_SSH_SSHKEYGEN} -q -t dsa -N '' -f $@ ; \ - fi - -t8: $(OBJ)/t8.out - set -xe ; if ssh -Q key | grep -q "^ssh-dss" ; then \ - ${TEST_SSH_SSHKEYGEN} -lf $(OBJ)/t8.out > /dev/null ; \ - ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t8.out > /dev/null ; \ - fi - $(OBJ)/t9.out: ! ${TEST_SSH_SSH} -Q key-plain | grep ecdsa >/dev/null || \ ${TEST_SSH_SSHKEYGEN} -q -t ecdsa -N '' -f $@ @@ -240,7 +222,7 @@ t10: $(OBJ)/t10.out ${TEST_SSH_SSHKEYGEN} -Bf $(OBJ)/t10.out > /dev/null t11: - set -xe ; if ${TEST_SSH_SSH} -Q key | grep -q "^ssh-dss" ; then \ + set -xe ; if ${TEST_SSH_SSH} -Q key | grep -q "^ssh-rsa" ; then \ ${TEST_SSH_SSHKEYGEN} -E sha256 -lf ${.CURDIR}/rsa_openssh.pub |\ awk '{print $$2}' | diff - ${.CURDIR}/t11.ok ; \ fi diff --git a/regress/agent.sh b/regress/agent.sh index f0022aca5528..26d4c9ed44b2 100644 --- a/regress/agent.sh +++ b/regress/agent.sh @@ -1,4 +1,4 @@ -# $OpenBSD: agent.sh,v 1.22 2024/10/24 03:28:34 djm Exp $ +# $OpenBSD: agent.sh,v 1.23 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="simple agent test" @@ -86,10 +86,6 @@ fi for t in ${SSH_KEYTYPES}; do trace "connect via agent using $t key" - if [ "$t" = "ssh-dss" ]; then - echo "PubkeyAcceptedAlgorithms +ssh-dss" >> $OBJ/ssh_proxy - echo "PubkeyAcceptedAlgorithms +ssh-dss" >> $OBJ/sshd_proxy - fi ${SSH} -F $OBJ/ssh_proxy -i $OBJ/$t-agent.pub -oIdentitiesOnly=yes \ somehost exit 52 r=$? @@ -143,7 +139,6 @@ fi (printf 'cert-authority,principals="estragon" '; cat $OBJ/user_ca_key.pub) \ > $OBJ/authorized_keys_$USER for t in ${SSH_KEYTYPES}; do - if [ "$t" != "ssh-dss" ]; then trace "connect via agent using $t key" ${SSH} -F $OBJ/ssh_proxy -i $OBJ/$t-agent.pub \ -oCertificateFile=$OBJ/$t-agent-cert.pub \ @@ -152,7 +147,6 @@ for t in ${SSH_KEYTYPES}; do if [ $r -ne 52 ]; then fail "ssh connect with failed (exit code $r)" fi - fi done ## Deletion tests. diff --git a/regress/cert-hostkey.sh b/regress/cert-hostkey.sh index a3414e1a5c50..bfdd3588d98f 100644 --- a/regress/cert-hostkey.sh +++ b/regress/cert-hostkey.sh @@ -1,4 +1,4 @@ -# $OpenBSD: cert-hostkey.sh,v 1.27 2021/09/30 05:26:26 dtucker Exp $ +# $OpenBSD: cert-hostkey.sh,v 1.28 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="certified host keys" @@ -70,7 +70,7 @@ touch $OBJ/host_revoked_plain touch $OBJ/host_revoked_cert cat $OBJ/host_ca_key.pub $OBJ/host_ca_key2.pub > $OBJ/host_revoked_ca -PLAIN_TYPES=`echo "$SSH_KEYTYPES" | sed 's/^ssh-dss/ssh-dsa/g;s/^ssh-//'` +PLAIN_TYPES=`echo "$SSH_KEYTYPES" | sed 's/^ssh-//'` if echo "$PLAIN_TYPES" | grep '^rsa$' >/dev/null 2>&1 ; then PLAIN_TYPES="$PLAIN_TYPES rsa-sha2-256 rsa-sha2-512" diff --git a/regress/cert-userkey.sh b/regress/cert-userkey.sh index 2ab0a1e9e65c..fde2caefbd4f 100644 --- a/regress/cert-userkey.sh +++ b/regress/cert-userkey.sh @@ -1,4 +1,4 @@ -# $OpenBSD: cert-userkey.sh,v 1.29 2024/12/06 16:25:58 djm Exp $ +# $OpenBSD: cert-userkey.sh,v 1.30 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="certified user keys" @@ -10,7 +10,7 @@ cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak grep -v AuthorizedKeysFile $OBJ/sshd_proxy > $OBJ/sshd_proxy_bak echo "AuthorizedKeysFile $OBJ/authorized_keys_%u_*" >> $OBJ/sshd_proxy_bak -PLAIN_TYPES=`$SSH -Q key-plain | maybe_filter_sk | sed 's/^ssh-dss/ssh-dsa/;s/^ssh-//'` +PLAIN_TYPES=`$SSH -Q key-plain | maybe_filter_sk | sed 's/^ssh-//'` EXTRA_TYPES="" rsa="" @@ -25,7 +25,7 @@ kname() { sk-ecdsa-*) n="sk-ecdsa" ;; sk-ssh-ed25519*) n="sk-ssh-ed25519" ;; # subshell because some seds will add a newline - *) n=$(echo $1 | sed 's/^dsa/ssh-dss/;s/^rsa/ssh-rsa/;s/^ed/ssh-ed/') ;; + *) n=$(echo $1 | sed 's/^rsa/ssh-rsa/;s/^ed/ssh-ed/') ;; esac if [ -z "$rsa" ]; then echo "$n*,ssh-ed25519*" diff --git a/regress/dsa_ssh2.prv b/regress/dsa_ssh2.prv deleted file mode 100644 index c93b4037194c..000000000000 --- a/regress/dsa_ssh2.prv +++ /dev/null @@ -1,14 +0,0 @@ ----- BEGIN SSH2 ENCRYPTED PRIVATE KEY ---- -Subject: ssh-keygen test -Comment: "1024-bit dsa, Tue Jan 08 2002 22:00:23 +0100" -P2/56wAAAgIAAAAmZGwtbW9kcHtzaWdue2RzYS1uaXN0LXNoYTF9LGRoe3BsYWlufX0AAA -AEbm9uZQAAAcQAAAHAAAAAAAAABACwUfm3AxZTut3icBmwCcD48nY64HzuELlQ+vEqjIcR -Lo49es/DQTeLNQ+kdKRCfouosGNv0WqxRtF0tUsWdXxS37oHGa4QPugBdHRd7YlZGZv8kg -x7FsoepY7v7E683/97dv2zxL3AGagTEzWr7fl0yPexAaZoDvtQrrjX44BLmwAABACWQkvv -MxnD8eFkS1konFfMJ1CkuRfTN34CBZ6dY7VTSGemy4QwtFdMKmoufD0eKgy3p5WOeWCYKt -F4FhjHKZk/aaxFjjIbtkrnlvXg64QI11dSZyBN6/ViQkHPSkUDF+A6AAEhrNbQbAFSvao1 -kTvNtPCtL0AkUIduEMzGQfLCTAAAAKDeC043YVo9Zo0zAEeIA4uZh4LBCQAAA/9aj7Y5ik -ehygJ4qTDSlVypsPuV+n59tMS0e2pfrSG87yf5r94AKBmJeho5OO6wYaXCxsVB7AFbSUD6 -75AK8mHF4v1/+7SWKk5f8xlMCMSPZ9K0+j/W1d/q2qkhnnDZolOHDomLA+U00i5ya/jnTV -zyDPWLFpWK8u3xGBPAYX324gAAAKDHFvooRnaXdZbeWGTTqmgHB1GU9A== ----- END SSH2 ENCRYPTED PRIVATE KEY ---- diff --git a/regress/dsa_ssh2.pub b/regress/dsa_ssh2.pub deleted file mode 100644 index 215d73baef31..000000000000 --- a/regress/dsa_ssh2.pub +++ /dev/null @@ -1,13 +0,0 @@ ----- BEGIN SSH2 PUBLIC KEY ---- -Subject: ssh-keygen test -Comment: "1024-bit dsa, Tue Jan 08 2002 22:00:23 +0100" -AAAAB3NzaC1kc3MAAACBALBR+bcDFlO63eJwGbAJwPjydjrgfO4QuVD68SqMhxEujj16z8 -NBN4s1D6R0pEJ+i6iwY2/RarFG0XS1SxZ1fFLfugcZrhA+6AF0dF3tiVkZm/ySDHsWyh6l -ju/sTrzf/3t2/bPEvcAZqBMTNavt+XTI97EBpmgO+1CuuNfjgEubAAAAFQDeC043YVo9Zo -0zAEeIA4uZh4LBCQAAAIEAlkJL7zMZw/HhZEtZKJxXzCdQpLkX0zd+AgWenWO1U0hnpsuE -MLRXTCpqLnw9HioMt6eVjnlgmCrReBYYxymZP2msRY4yG7ZK55b14OuECNdXUmcgTev1Yk -JBz0pFAxfgOgABIazW0GwBUr2qNZE7zbTwrS9AJFCHbhDMxkHywkwAAACAWo+2OYpHocoC -eKkw0pVcqbD7lfp+fbTEtHtqX60hvO8n+a/eACgZiXoaOTjusGGlwsbFQewBW0lA+u+QCv -JhxeL9f/u0lipOX/MZTAjEj2fStPo/1tXf6tqpIZ5w2aJThw6JiwPlNNIucmv4501c8gz1 -ixaVivLt8RgTwGF99uI= ----- END SSH2 PUBLIC KEY ---- diff --git a/regress/hostbased.sh b/regress/hostbased.sh index eb9cf2727d33..5de176b18bf7 100644 --- a/regress/hostbased.sh +++ b/regress/hostbased.sh @@ -1,4 +1,4 @@ -# $OpenBSD: hostbased.sh,v 1.4 2022/12/07 11:45:43 dtucker Exp $ +# $OpenBSD: hostbased.sh,v 1.5 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. # This test requires external setup and thus is skipped unless @@ -43,7 +43,6 @@ for key in `${SUDO} ${SSHD} -T | awk '$1=="hostkey"{print $2}'`; do 521*ECDSA*) algos="$algos ecdsa-sha2-nistp521" ;; *RSA*) algos="$algos ssh-rsa rsa-sha2-256 rsa-sha2-512" ;; *ED25519*) algos="$algos ssh-ed25519" ;; - *DSA*) algos="$algos ssh-dss" ;; *) verbose "unknown host key type $key" ;; esac done diff --git a/regress/keytype.sh b/regress/keytype.sh index f1c045183bd3..11ef7d0cb270 100644 --- a/regress/keytype.sh +++ b/regress/keytype.sh @@ -1,4 +1,4 @@ -# $OpenBSD: keytype.sh,v 1.11 2021/02/25 03:27:34 djm Exp $ +# $OpenBSD: keytype.sh,v 1.12 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="login with different key types" @@ -10,7 +10,6 @@ cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak ktypes="" for i in ${SSH_KEYTYPES}; do case "$i" in - ssh-dss) ktypes="$ktypes dsa-1024" ;; ssh-rsa) ktypes="$ktypes rsa-2048 rsa-3072" ;; ssh-ed25519) ktypes="$ktypes ed25519-512" ;; ecdsa-sha2-nistp256) ktypes="$ktypes ecdsa-256" ;; @@ -36,7 +35,6 @@ done kname_to_ktype() { case $1 in - dsa-1024) echo ssh-dss;; ecdsa-256) echo ecdsa-sha2-nistp256;; ecdsa-384) echo ecdsa-sha2-nistp384;; ecdsa-521) echo ecdsa-sha2-nistp521;; diff --git a/regress/knownhosts-command.sh b/regress/knownhosts-command.sh index 8472ec8121c5..2ed6fa05e06a 100644 --- a/regress/knownhosts-command.sh +++ b/regress/knownhosts-command.sh @@ -1,4 +1,4 @@ -# $OpenBSD: knownhosts-command.sh,v 1.3 2021/08/30 01:15:45 djm Exp $ +# $OpenBSD: knownhosts-command.sh,v 1.4 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="known hosts command " @@ -40,7 +40,6 @@ ${SSH} -F $OBJ/ssh_proxy x true && fail "ssh connect succeeded with bad exit" for keytype in ${SSH_HOSTKEY_TYPES} ; do algs=$keytype - test "x$keytype" = "xssh-dss" && continue test "x$keytype" = "xssh-rsa" && algs=ssh-rsa,rsa-sha2-256,rsa-sha2-512 verbose "keytype $keytype" cat > $OBJ/knownhosts_command << _EOF diff --git a/regress/krl.sh b/regress/krl.sh index d560d61e8ce1..37d9f171a2c0 100644 --- a/regress/krl.sh +++ b/regress/krl.sh @@ -1,4 +1,4 @@ -# $OpenBSD: krl.sh,v 1.12 2023/01/16 04:11:29 djm Exp $ +# $OpenBSD: krl.sh,v 1.13 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="key revocation lists" @@ -11,7 +11,6 @@ for t in $SSH_KEYTYPES; do case "$t" in ecdsa*) ktype2=ecdsa ;; ssh-rsa) ktype3=rsa ;; - ssh-dss) ktype4=dsa ;; sk-ssh-ed25519@openssh.com) ktype5=ed25519-sk ;; sk-ecdsa-sha2-nistp256@openssh.com) ktype6=ecdsa-sk ;; esac diff --git a/regress/limit-keytype.sh b/regress/limit-keytype.sh index 7127de007cc6..2f5b63a4831c 100644 --- a/regress/limit-keytype.sh +++ b/regress/limit-keytype.sh @@ -1,4 +1,4 @@ -# $OpenBSD: limit-keytype.sh,v 1.10 2021/02/25 03:27:34 djm Exp $ +# $OpenBSD: limit-keytype.sh,v 1.11 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="restrict pubkey type" @@ -17,7 +17,6 @@ for t in $SSH_KEYTYPES ; do case "$t" in ssh-rsa) ktype2=rsa ;; ecdsa*) ktype3=ecdsa ;; # unused - ssh-dss) ktype4=dsa ;; sk-ssh-ed25519@openssh.com) ktype5=ed25519-sk ;; sk-ecdsa-sha2-nistp256@openssh.com) ktype6=ecdsa-sk ;; esac @@ -75,7 +74,6 @@ keytype() { case "$1" in ecdsa) printf "ecdsa-sha2-*" ;; ed25519) printf "ssh-ed25519" ;; - dsa) printf "ssh-dss" ;; rsa) printf "rsa-sha2-256,rsa-sha2-512,ssh-rsa" ;; sk-ecdsa) printf "sk-ecdsa-*" ;; sk-ssh-ed25519) printf "sk-ssh-ed25519-*" ;; @@ -123,7 +121,7 @@ if [ "$ktype1" != "$ktype2" ]; then fi ${SSH} $opts -i $OBJ/user_key2 proxy true || fatal "key2 failed" -# Allow only DSA in main config, Ed25519 for user. +# Allow only Ed25519 in main config, Ed25519 for user. verbose "match w/ matching" prepare_config "PubkeyAcceptedAlgorithms `keytype $ktype4`" \ "Match user $USER" "PubkeyAcceptedAlgorithms +`keytype $ktype1`" diff --git a/regress/misc/fuzz-harness/fixed-keys.h b/regress/misc/fuzz-harness/fixed-keys.h index c6e7c6cc1828..7dae9ac0034d 100644 --- a/regress/misc/fuzz-harness/fixed-keys.h +++ b/regress/misc/fuzz-harness/fixed-keys.h @@ -34,41 +34,6 @@ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDf56l/5UYqgY9oBlet/pLRzK6ZCd12QYGdUVfQDl6HftG0u6DSpjm2HGwFRsYZWv2ZN3ZBfAu6MHBiDmXUw/8WaD7nfXZmDH2keZL6opQttqvSGU2Cm00Rv5o1R3ej2qDdpepebv5meMBXTl5/+bE1E3Zm+4STDtxGmlMlxsEj68XeVe4JedfaSUMj3kaXYBbdYdG1qeosdle4GSONEEMpzsxSr8Y/WGYuIB33l29Tt9mNGUgSw/zjMYQjUVvQv+SY8dw62JV8d+3wK2YL2/r73gms6I8EE1JxX53KuAAY+x0p2v/W8ilCYI2Ijyzc8KIPwntmIFpibQjx+rkb+qdT" #define CERT_RSA \ "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg89JX6OBMYDSxER8fnU5y8xxeMCHR/hI0uVqdEhNyCpcAAAADAQABAAABAQDf56l/5UYqgY9oBlet/pLRzK6ZCd12QYGdUVfQDl6HftG0u6DSpjm2HGwFRsYZWv2ZN3ZBfAu6MHBiDmXUw/8WaD7nfXZmDH2keZL6opQttqvSGU2Cm00Rv5o1R3ej2qDdpepebv5meMBXTl5/+bE1E3Zm+4STDtxGmlMlxsEj68XeVe4JedfaSUMj3kaXYBbdYdG1qeosdle4GSONEEMpzsxSr8Y/WGYuIB33l29Tt9mNGUgSw/zjMYQjUVvQv+SY8dw62JV8d+3wK2YL2/r73gms6I8EE1JxX53KuAAY+x0p2v/W8ilCYI2Ijyzc8KIPwntmIFpibQjx+rkb+qdTAAAAAAAAA+0AAAABAAAAB3VseXNzZXMAAAAXAAAAB3VseXNzZXMAAAAIb2R5c3NldXMAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgM9BeYRUxUuZ4VHJp8oxVaA8OS/z+5EFPCZwQNq1nMwMAAABTAAAAC3NzaC1lZDI1NTE5AAAAQGCDA6PWw4x9bHQl0w7NqifHepumqD3dmyMx+hZGuPRon+TsyCjfytu7hWmV7l9XUF0fPQNFQ7FGat5e+7YUNgE= id_rsa.pub" -#define PRIV_DSA \ -"-----BEGIN OPENSSH PRIVATE KEY-----\n"\ -"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsgAAAAdzc2gtZH\n"\ -"NzAAAAgQCsGTfjpQ465EOkfQXJM9BOvfRQE0fqlykAls+ncz+T7hrbeScRu8xpwzsznJNm\n"\ -"xlW8o6cUDiHmBJ5OHgamUC9N7YJeU/6fnOAZifgN8mqK6k8pKHuje8ANOiYgHLl0yiASQA\n"\ -"3//qMyzZ+W/hemoLSmLAbEqlfWVeyYx+wta1Vm+QAAABUAvWyehvUvdHvQxavYgS5p0t5Q\n"\ -"d7UAAACBAIRA9Yy+f4Kzqpv/qICPO3zk42UuP7WAhSW2nCbQdLlCiSTxcjKgcvXNRckwJP\n"\ -"44JjSHOtJy/AMtJrPIbLYG6KuWTdBlEHFiG6DafvLG+qPMSL2bPjXTOhuOMbCHIZ+5WBkW\n"\ -"THeG/Nv11iI01Of9V6tXkig23K370flkRkXFi9MdAAAAgCt6YUcQkNwG7B/e5M1FZsLP9O\n"\ -"kVB3BwLAOjmWdHpyhu3HpwSJa3XLEvhXN0i6IVI2KgPo/2GtYA6rHt14L+6u1pmhh8sAvQ\n"\ -"ksp3qZB+xh/NP+hBqf0sbHX0yYbzKOvI5SCc/kKK6yagcBZOsubM/KC8TxyVgmD5c6WzYs\n"\ -"h5TEpvAAAB2PHjRbbx40W2AAAAB3NzaC1kc3MAAACBAKwZN+OlDjrkQ6R9Bckz0E699FAT\n"\ -"R+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4B\n"\ -"mJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1r\n"\ -"VWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS\n"\ -"4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIb\n"\ -"oNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRc\n"\ -"WL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SL\n"\ -"ohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJ\n"\ -"z+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8AAAAUUA+OGldMi76ClO/sstpdbBUE\n"\ -"lq8AAAAAAQI=\n"\ -"-----END OPENSSH PRIVATE KEY-----\n" -#define PUB_DSA \ -"ssh-dss AAAAB3NzaC1kc3MAAACBAKwZN+OlDjrkQ6R9Bckz0E699FATR+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4BmJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1rVWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIboNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRcWL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SLohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJz+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8=" -#define CERT_DSA \ -"ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAguF716Yub+vVKNlONKLsfxGYWkRe/PyjfYdGRTsFaDvAAAACBAKwZN+OlDjrkQ6R9Bckz0E699FATR+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4BmJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1rVWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIboNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRcWL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SLohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJz+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8AAAAAAAAD6AAAAAEAAAAHdWx5c3NlcwAAABcAAAAHdWx5c3NlcwAAAAhvZHlzc2V1cwAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAFMAAAALc3NoLWVkMjU1MTkAAABAjMQEZcbdUYJBjIC4GxByFDOb8tv71vDZdx7irHwaqIjx5rzpJUuOV1r8ZO4kY+Yaiun1yrWj2QYkfJrHBvD1DA== id_dsa.pub" -#define PRIV_ECDSA \ -"-----BEGIN OPENSSH PRIVATE KEY-----\n"\ -"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n"\ -"1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTDJ0VlMv+0rguNzaJ1DF2KueHaxRSQ\n"\ -"6LpIxGbulrg1a8RPbnMXwag5GcDiDllD2lDUJUuBEWyjXA0rZoZX35ELAAAAoE/Bbr5PwW\n"\ -"6+AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMMnRWUy/7SuC43N\n"\ -"onUMXYq54drFFJDoukjEZu6WuDVrxE9ucxfBqDkZwOIOWUPaUNQlS4ERbKNcDStmhlffkQ\n"\ -"sAAAAhAIhE6hCID5oOm1TDktc++KFKyScjLifcZ6Cgv5xSSyLOAAAAAAECAwQFBgc=\n"\ -"-----END OPENSSH PRIVATE KEY-----\n" #define PUB_ECDSA \ "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMMnRWUy/7SuC43NonUMXYq54drFFJDoukjEZu6WuDVrxE9ucxfBqDkZwOIOWUPaUNQlS4ERbKNcDStmhlffkQs=" #define CERT_ECDSA \ diff --git a/regress/misc/fuzz-harness/testdata/create-agent-corpus.sh b/regress/misc/fuzz-harness/testdata/create-agent-corpus.sh index 1043b9ff47d7..842b8c48d9dd 100755 --- a/regress/misc/fuzz-harness/testdata/create-agent-corpus.sh +++ b/regress/misc/fuzz-harness/testdata/create-agent-corpus.sh @@ -14,7 +14,7 @@ sleep 1 AGENT_PID=$! trap "kill $AGENT_PID" EXIT -PRIV="id_dsa id_ecdsa id_ecdsa_sk id_ed25519 id_ed25519_sk id_rsa" +PRIV="id_ecdsa id_ecdsa_sk id_ed25519 id_ed25519_sk id_rsa" # add keys ssh-add $PRIV diff --git a/regress/misc/fuzz-harness/testdata/id_dsa-cert.pub b/regress/misc/fuzz-harness/testdata/id_dsa-cert.pub deleted file mode 100644 index 3afb87fe62f7..000000000000 --- a/regress/misc/fuzz-harness/testdata/id_dsa-cert.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAguF716Yub+vVKNlONKLsfxGYWkRe/PyjfYdGRTsFaDvAAAACBAKwZN+OlDjrkQ6R9Bckz0E699FATR+qXKQCWz6dzP5PuGtt5JxG7zGnDOzOck2bGVbyjpxQOIeYEnk4eBqZQL03tgl5T/p+c4BmJ+A3yaorqTykoe6N7wA06JiAcuXTKIBJADf/+ozLNn5b+F6agtKYsBsSqV9ZV7JjH7C1rVWb5AAAAFQC9bJ6G9S90e9DFq9iBLmnS3lB3tQAAAIEAhED1jL5/grOqm/+ogI87fOTjZS4/tYCFJbacJtB0uUKJJPFyMqBy9c1FyTAk/jgmNIc60nL8Ay0ms8hstgboq5ZN0GUQcWIboNp+8sb6o8xIvZs+NdM6G44xsIchn7lYGRZMd4b82/XWIjTU5/1Xq1eSKDbcrfvR+WRGRcWL0x0AAACAK3phRxCQ3AbsH97kzUVmws/06RUHcHAsA6OZZ0enKG7cenBIlrdcsS+Fc3SLohUjYqA+j/Ya1gDqse3Xgv7q7WmaGHywC9CSynepkH7GH80/6EGp/SxsdfTJhvMo68jlIJz+QorrJqBwFk6y5sz8oLxPHJWCYPlzpbNiyHlMSm8AAAAAAAAD6AAAAAEAAAAHdWx5c3NlcwAAABcAAAAHdWx5c3NlcwAAAAhvZHlzc2V1cwAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAz0F5hFTFS5nhUcmnyjFVoDw5L/P7kQU8JnBA2rWczAwAAAFMAAAALc3NoLWVkMjU1MTkAAABAjMQEZcbdUYJBjIC4GxByFDOb8tv71vDZdx7irHwaqIjx5rzpJUuOV1r8ZO4kY+Yaiun1yrWj2QYkfJrHBvD1DA== id_dsa.pub diff --git a/regress/misc/ssh-verify-attestation/Makefile b/regress/misc/ssh-verify-attestation/Makefile index 2a797aecae46..06fb8aac4e98 100644 --- a/regress/misc/ssh-verify-attestation/Makefile +++ b/regress/misc/ssh-verify-attestation/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.1 2024/12/04 16:42:49 djm Exp $ +# $OpenBSD: Makefile,v 1.2 2025/05/06 06:05:48 djm Exp $ .include .include @@ -13,7 +13,7 @@ SRCS=ssh-verify-attestation.c # From usr.bin/ssh SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c -SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c +SRCS+=ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c SRCS+=addr.c addrmatch.c bitmap.c SRCS+=ed25519.c hash.c diff --git a/regress/ssh-com-client.sh b/regress/ssh-com-client.sh index e4f80cf0aadf..97b36b564f4a 100644 --- a/regress/ssh-com-client.sh +++ b/regress/ssh-com-client.sh @@ -1,4 +1,4 @@ -# $OpenBSD: ssh-com-client.sh,v 1.7 2013/05/17 04:29:14 dtucker Exp $ +# $OpenBSD: ssh-com-client.sh,v 1.8 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="connect with ssh.com client" @@ -28,7 +28,7 @@ VERSIONS=" # setup authorized keys SRC=`dirname ${SCRIPT}` -cp ${SRC}/dsa_ssh2.prv ${OBJ}/id.com +cp ${SRC}/rsa_ssh2.prv ${OBJ}/id.com chmod 600 ${OBJ}/id.com ${SSHKEYGEN} -i -f ${OBJ}/id.com > $OBJ/id.openssh chmod 600 ${OBJ}/id.openssh @@ -36,8 +36,8 @@ ${SSHKEYGEN} -y -f ${OBJ}/id.openssh > $OBJ/authorized_keys_$USER ${SSHKEYGEN} -e -f ${OBJ}/id.openssh > $OBJ/id.com.pub echo IdKey ${OBJ}/id.com > ${OBJ}/id.list -# we need a DSA host key -t=dsa +# we need a RSA host key +t=rsa rm -f ${OBJ}/$t ${OBJ}/$t.pub ${SSHKEYGEN} -q -N '' -t $t -f ${OBJ}/$t $SUDO cp $OBJ/$t $OBJ/host.$t @@ -47,7 +47,6 @@ echo HostKey $OBJ/host.$t >> $OBJ/sshd_config mkdir -p ${OBJ}/${USER}/hostkeys HK=${OBJ}/${USER}/hostkeys/key_${PORT}_127.0.0.1 ${SSHKEYGEN} -e -f ${OBJ}/rsa.pub > ${HK}.ssh-rsa.pub -${SSHKEYGEN} -e -f ${OBJ}/dsa.pub > ${HK}.ssh-dss.pub cat > ${OBJ}/ssh2_config << EOF *: @@ -74,7 +73,7 @@ for v in ${VERSIONS}; do continue fi verbose "ssh2 ${v}" - key=ssh-dss + key=ssh-rsa skipcat=0 case $v in 2.1.*|2.3.0) @@ -124,7 +123,6 @@ for v in ${VERSIONS}; do done rm -rf ${OBJ}/${USER} -for i in ssh2_config random_seed dsa.pub dsa host.dsa \ - id.list id.com id.com.pub id.openssh; do +for i in ssh2_config random_seed id.list id.com id.com.pub id.openssh; do rm -f ${OBJ}/$i done diff --git a/regress/ssh-com.sh b/regress/ssh-com.sh index b1a2505d1135..bb833380eb57 100644 --- a/regress/ssh-com.sh +++ b/regress/ssh-com.sh @@ -1,4 +1,4 @@ -# $OpenBSD: ssh-com.sh,v 1.10 2017/05/08 01:52:49 djm Exp $ +# $OpenBSD: ssh-com.sh,v 1.11 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="connect to ssh.com server" @@ -41,8 +41,8 @@ cat << EOF > $OBJ/sshd2_config PubKeyAuthentication yes #AllowedAuthentications publickey AuthorizationFile authorization - HostKeyFile ${SRC}/dsa_ssh2.prv - PublicHostKeyFile ${SRC}/dsa_ssh2.pub + HostKeyFile ${SRC}/rsa_ssh2.prv + PublicHostKeyFile ${SRC}/rsa_ssh2.pub RandomSeedFile ${OBJ}/random_seed MaxConnections 0 PermitRootLogin yes @@ -55,23 +55,21 @@ EOF sed "s/HostKeyAlias.*/HostKeyAlias ssh2-localhost-with-alias/" \ < $OBJ/ssh_config > $OBJ/ssh_config_com -# we need a DSA key for -rm -f ${OBJ}/dsa ${OBJ}/dsa.pub -${SSHKEYGEN} -q -N '' -t dsa -f ${OBJ}/dsa +# we need a RSA key for +rm -f ${OBJ}/rsa ${OBJ}/rsa.pub +${SSHKEYGEN} -q -N '' -t rsa -f ${OBJ}/rsa # setup userdir, try rsa first mkdir -p ${OBJ}/${USER} cp /dev/null ${OBJ}/${USER}/authorization -for t in rsa dsa; do - ${SSHKEYGEN} -e -f ${OBJ}/$t.pub > ${OBJ}/${USER}/$t.com - echo Key $t.com >> ${OBJ}/${USER}/authorization - echo IdentityFile ${OBJ}/$t >> ${OBJ}/ssh_config_com -done +${SSHKEYGEN} -e -f ${OBJ}/rsa.pub > ${OBJ}/${USER}/rsa.com +echo Key rsa.com >> ${OBJ}/${USER}/authorization +echo IdentityFile ${OBJ}/rsa >> ${OBJ}/ssh_config_com -# convert and append DSA hostkey +# convert and append RSA hostkey ( printf 'ssh2-localhost-with-alias,127.0.0.1,::1 ' - ${SSHKEYGEN} -if ${SRC}/dsa_ssh2.pub + ${SSHKEYGEN} -if ${SRC}/rsa_ssh2.pub ) >> $OBJ/known_hosts # go for it @@ -114,6 +112,6 @@ done rm -rf ${OBJ}/${USER} for i in sshd_config_proxy ssh_config_proxy random_seed \ - sshd2_config dsa.pub dsa ssh_config_com; do + sshd2_config rsa.pub rsa ssh_config_com; do rm -f ${OBJ}/$i done diff --git a/regress/ssh2putty.sh b/regress/ssh2putty.sh index 9b08310391ca..bd291313f6c3 100755 --- a/regress/ssh2putty.sh +++ b/regress/ssh2putty.sh @@ -1,5 +1,5 @@ #!/bin/sh -# $OpenBSD: ssh2putty.sh,v 1.9 2021/07/25 12:13:03 dtucker Exp $ +# $OpenBSD: ssh2putty.sh,v 1.10 2025/05/06 06:05:48 djm Exp $ if test "x$1" = "x" -o "x$2" = "x" -o "x$3" = "x" ; then echo "Usage: ssh2putty hostname port ssh-private-key" @@ -12,7 +12,6 @@ KEYFILE=$3 OPENSSL_BIN="${OPENSSL_BIN:-openssl}" -# XXX - support DSA keys too if grep "BEGIN RSA PRIVATE KEY" $KEYFILE >/dev/null 2>&1 ; then : else diff --git a/regress/sshcfgparse.sh b/regress/sshcfgparse.sh index 504853d32db5..29fa1d839be2 100644 --- a/regress/sshcfgparse.sh +++ b/regress/sshcfgparse.sh @@ -1,15 +1,8 @@ -# $OpenBSD: sshcfgparse.sh,v 1.9 2021/06/08 07:05:27 dtucker Exp $ +# $OpenBSD: sshcfgparse.sh,v 1.10 2025/05/06 06:05:48 djm Exp $ # Placed in the Public Domain. tid="ssh config parse" -dsa=0 -for t in $SSH_KEYTYPES; do - case "$t" in - ssh-dss) dsa=1 ;; - esac -done - expect_result_present() { _str="$1" ; shift for _expect in "$@" ; do @@ -66,33 +59,23 @@ verbose "pubkeyacceptedalgorithms" # Default set f=`${SSH} -GF none host | awk '/^pubkeyacceptedalgorithms /{print $2}'` expect_result_present "$f" "ssh-ed25519" "ssh-ed25519-cert-v01.*" -expect_result_absent "$f" "ssh-dss" # Explicit override f=`${SSH} -GF none -opubkeyacceptedalgorithms=ssh-ed25519 host | \ awk '/^pubkeyacceptedalgorithms /{print $2}'` expect_result_present "$f" "ssh-ed25519" -expect_result_absent "$f" "ssh-ed25519-cert-v01.*" "ssh-dss" +expect_result_absent "$f" "ssh-ed25519-cert-v01.*" # Removal from default set f=`${SSH} -GF none -opubkeyacceptedalgorithms=-ssh-ed25519-cert* host | \ awk '/^pubkeyacceptedalgorithms /{print $2}'` expect_result_present "$f" "ssh-ed25519" -expect_result_absent "$f" "ssh-ed25519-cert-v01.*" "ssh-dss" +expect_result_absent "$f" "ssh-ed25519-cert-v01.*" f=`${SSH} -GF none -opubkeyacceptedalgorithms=-ssh-ed25519 host | \ awk '/^pubkeyacceptedalgorithms /{print $2}'` expect_result_present "$f" "ssh-ed25519-cert-v01.*" -expect_result_absent "$f" "ssh-ed25519" "ssh-dss" +expect_result_absent "$f" "ssh-ed25519" # Append to default set. # This is not tested when built !WITH_OPENSSL -if [ "$dsa" = "1" ]; then - f=`${SSH} -GF none -opubkeyacceptedalgorithms=+ssh-dss-cert* host | \ - awk '/^pubkeyacceptedalgorithms /{print $2}'` - expect_result_present "$f" "ssh-ed25519" "ssh-dss-cert-v01.*" - expect_result_absent "$f" "ssh-dss" - f=`${SSH} -GF none -opubkeyacceptedalgorithms=+ssh-dss host | \ - awk '/^pubkeyacceptedalgorithms /{print $2}'` - expect_result_present "$f" "ssh-ed25519" "ssh-ed25519-cert-v01.*" "ssh-dss" - expect_result_absent "$f" "ssh-dss-cert-v01.*" -fi +# XXX need a test for this verbose "agentforwarding" f=`${SSH} -GF none host | awk '/^forwardagent /{print$2}'` diff --git a/regress/unittests/Makefile.inc b/regress/unittests/Makefile.inc index ad7fdad84a53..5fcf7a950a39 100644 --- a/regress/unittests/Makefile.inc +++ b/regress/unittests/Makefile.inc @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile.inc,v 1.17 2025/04/15 04:00:42 djm Exp $ +# $OpenBSD: Makefile.inc,v 1.18 2025/05/06 06:05:48 djm Exp $ .include .include @@ -18,10 +18,6 @@ TEST_ENV?= MALLOC_OPTIONS=${MALLOC_OPTIONS} OPENSSL?= yes DSAKEY?= yes -.if (${DSAKEY:L} == "yes") -CFLAGS+= -DWITH_DSA -.endif - .if (${OPENSSL:L} == "yes") CFLAGS+= -DWITH_OPENSSL .endif diff --git a/regress/unittests/authopt/Makefile b/regress/unittests/authopt/Makefile index 8bed7a915dfa..d5ea2c796be1 100644 --- a/regress/unittests/authopt/Makefile +++ b/regress/unittests/authopt/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.8 2025/04/15 04:00:42 djm Exp $ +# $OpenBSD: Makefile,v 1.9 2025/05/06 06:05:48 djm Exp $ PROG=test_authopt SRCS=tests.c @@ -8,7 +8,7 @@ SRCS+=auth-options.c # From usr.bin/ssh SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c -SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c +SRCS+=ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c SRCS+=addr.c addrmatch.c bitmap.c SRCS+=ed25519.c hash.c diff --git a/regress/unittests/hostkeys/Makefile b/regress/unittests/hostkeys/Makefile index 79a9d5745419..142ffa632aad 100644 --- a/regress/unittests/hostkeys/Makefile +++ b/regress/unittests/hostkeys/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.11 2025/04/15 04:00:42 djm Exp $ +# $OpenBSD: Makefile,v 1.12 2025/05/06 06:05:48 djm Exp $ PROG=test_hostkeys SRCS=tests.c test_iterate.c @@ -6,7 +6,7 @@ SRCS=tests.c test_iterate.c # From usr.bin/ssh SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c -SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c +SRCS+=ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c SRCS+=addr.c addrmatch.c bitmap.c hostfile.c SRCS+=ed25519.c hash.c diff --git a/regress/unittests/hostkeys/mktestdata.sh b/regress/unittests/hostkeys/mktestdata.sh index 5a46de990dca..5fec5829853a 100644 --- a/regress/unittests/hostkeys/mktestdata.sh +++ b/regress/unittests/hostkeys/mktestdata.sh @@ -1,11 +1,11 @@ #!/bin/sh -# $OpenBSD: mktestdata.sh,v 1.2 2017/04/30 23:33:48 djm Exp $ +# $OpenBSD: mktestdata.sh,v 1.3 2025/05/06 06:05:48 djm Exp $ set -ex cd testdata -rm -f rsa* dsa* ecdsa* ed25519* +rm -f rsa* ecdsa* ed25519* rm -f known_hosts* gen_all() { @@ -14,11 +14,10 @@ gen_all() { test "x$_n" = "x1" && _ecdsa_bits=384 test "x$_n" = "x2" && _ecdsa_bits=521 ssh-keygen -qt rsa -b 1024 -C "RSA #$_n" -N "" -f rsa_$_n - ssh-keygen -qt dsa -b 1024 -C "DSA #$_n" -N "" -f dsa_$_n ssh-keygen -qt ecdsa -b $_ecdsa_bits -C "ECDSA #$_n" -N "" -f ecdsa_$_n ssh-keygen -qt ed25519 -C "ED25519 #$_n" -N "" -f ed25519_$_n # Don't need private keys - rm -f rsa_$_n dsa_$_n ecdsa_$_n ed25519_$_n + rm -f rsa_$_n ecdsa_$_n ed25519_$_n } hentries() { @@ -65,18 +64,18 @@ rm -f known_hosts_hash_frag.old echo "# Revoked and CA keys" printf "@revoked sisyphus.example.com " ; cat ed25519_4.pub printf "@cert-authority prometheus.example.com " ; cat ecdsa_4.pub - printf "@cert-authority *.example.com " ; cat dsa_4.pub + printf "@cert-authority *.example.com " ; cat rsa_4.pub printf "\n" echo "# Some invalid lines" # Invalid marker - printf "@what sisyphus.example.com " ; cat dsa_1.pub + printf "@what sisyphus.example.com " ; cat rsa_1.pub # Key missing echo "sisyphus.example.com " # Key blob missing echo "prometheus.example.com ssh-ed25519 " # Key blob truncated - echo "sisyphus.example.com ssh-dsa AAAATgAAAAdz" + echo "sisyphus.example.com ssh-rsa AAAATgAAAAdz" # Invalid type echo "sisyphus.example.com ssh-XXX AAAATgAAAAdzc2gtWFhYAAAAP0ZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRg==" # Type mismatch with blob diff --git a/regress/unittests/hostkeys/test_iterate.c b/regress/unittests/hostkeys/test_iterate.c index 7efb8e1b9cc6..0139376f4a78 100644 --- a/regress/unittests/hostkeys/test_iterate.c +++ b/regress/unittests/hostkeys/test_iterate.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_iterate.c,v 1.9 2024/01/11 01:45:58 djm Exp $ */ +/* $OpenBSD: test_iterate.c,v 1.10 2025/05/06 06:05:48 djm Exp $ */ /* * Regress test for hostfile.h hostkeys_foreach() * @@ -94,15 +94,8 @@ check(struct hostkey_foreach_line *l, void *_ctx) expected->no_parse_keytype == KEY_ECDSA) skip = 1; #endif /* OPENSSL_HAS_ECC */ -#ifndef WITH_DSA - if (expected->l.keytype == KEY_DSA || - expected->no_parse_keytype == KEY_DSA) - skip = 1; -#endif #ifndef WITH_OPENSSL - if (expected->l.keytype == KEY_DSA || - expected->no_parse_keytype == KEY_DSA || - expected->l.keytype == KEY_RSA || + if (expected->l.keytype == KEY_RSA || expected->no_parse_keytype == KEY_RSA || expected->l.keytype == KEY_ECDSA || expected->no_parse_keytype == KEY_ECDSA) @@ -160,14 +153,9 @@ prepare_expected(struct expected *expected, size_t n) if (expected[i].l.keytype == KEY_ECDSA) continue; #endif /* OPENSSL_HAS_ECC */ -#ifndef WITH_DSA - if (expected[i].l.keytype == KEY_DSA) - continue; -#endif #ifndef WITH_OPENSSL switch (expected[i].l.keytype) { case KEY_RSA: - case KEY_DSA: case KEY_ECDSA: continue; } @@ -204,23 +192,9 @@ struct expected expected_full[] = { NULL, /* comment */ 0, /* note */ } }, - { "dsa_1.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, { - NULL, - 2, - HKF_STATUS_OK, - 0, - NULL, - MRK_NONE, - "sisyphus.example.com", - NULL, - KEY_DSA, - NULL, /* filled at runtime */ - "DSA #1", - 0, - } }, { "ecdsa_1.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, { NULL, - 3, + 2, HKF_STATUS_OK, 0, NULL, @@ -234,7 +208,7 @@ struct expected expected_full[] = { } }, { "ed25519_1.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, { NULL, - 4, + 3, HKF_STATUS_OK, 0, NULL, @@ -248,7 +222,7 @@ struct expected expected_full[] = { } }, { "rsa_1.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, { NULL, - 5, + 4, HKF_STATUS_OK, 0, NULL, @@ -262,7 +236,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 6, + 5, HKF_STATUS_COMMENT, 0, "", @@ -276,7 +250,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 7, + 6, HKF_STATUS_COMMENT, 0, "# Plain host keys, hostnames + addresses", @@ -288,23 +262,9 @@ struct expected expected_full[] = { NULL, 0, } }, - { "dsa_2.pub" , -1, -1, HKF_MATCH_HOST, 0, HKF_MATCH_IP, HKF_MATCH_IP, -1, { - NULL, - 8, - HKF_STATUS_OK, - 0, - NULL, - MRK_NONE, - "prometheus.example.com,192.0.2.1,2001:db8::1", - NULL, - KEY_DSA, - NULL, /* filled at runtime */ - "DSA #2", - 0, - } }, { "ecdsa_2.pub" , -1, -1, HKF_MATCH_HOST, 0, HKF_MATCH_IP, HKF_MATCH_IP, -1, { NULL, - 9, + 7, HKF_STATUS_OK, 0, NULL, @@ -318,7 +278,7 @@ struct expected expected_full[] = { } }, { "ed25519_2.pub" , -1, -1, HKF_MATCH_HOST, 0, HKF_MATCH_IP, HKF_MATCH_IP, -1, { NULL, - 10, + 8, HKF_STATUS_OK, 0, NULL, @@ -332,7 +292,7 @@ struct expected expected_full[] = { } }, { "rsa_2.pub" , -1, -1, HKF_MATCH_HOST, 0, HKF_MATCH_IP, HKF_MATCH_IP, -1, { NULL, - 11, + 9, HKF_STATUS_OK, 0, NULL, @@ -346,7 +306,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 12, + 10, HKF_STATUS_COMMENT, 0, "", @@ -360,7 +320,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 13, + 11, HKF_STATUS_COMMENT, 0, "# Some hosts with wildcard names / IPs", @@ -372,23 +332,9 @@ struct expected expected_full[] = { NULL, 0, } }, - { "dsa_3.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, HKF_MATCH_IP, HKF_MATCH_IP, -1, { - NULL, - 14, - HKF_STATUS_OK, - 0, - NULL, - MRK_NONE, - "*.example.com,192.0.2.*,2001:*", - NULL, - KEY_DSA, - NULL, /* filled at runtime */ - "DSA #3", - 0, - } }, { "ecdsa_3.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, HKF_MATCH_IP, HKF_MATCH_IP, -1, { NULL, - 15, + 12, HKF_STATUS_OK, 0, NULL, @@ -402,7 +348,7 @@ struct expected expected_full[] = { } }, { "ed25519_3.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, HKF_MATCH_IP, HKF_MATCH_IP, -1, { NULL, - 16, + 13, HKF_STATUS_OK, 0, NULL, @@ -416,7 +362,7 @@ struct expected expected_full[] = { } }, { "rsa_3.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, HKF_MATCH_IP, HKF_MATCH_IP, -1, { NULL, - 17, + 14, HKF_STATUS_OK, 0, NULL, @@ -430,7 +376,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 18, + 15, HKF_STATUS_COMMENT, 0, "", @@ -444,7 +390,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 19, + 16, HKF_STATUS_COMMENT, 0, "# Hashed hostname and address entries", @@ -456,23 +402,9 @@ struct expected expected_full[] = { NULL, 0, } }, - { "dsa_5.pub" , -1, -1, 0, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, -1, { - NULL, - 20, - HKF_STATUS_OK, - 0, - NULL, - MRK_NONE, - NULL, - NULL, - KEY_DSA, - NULL, /* filled at runtime */ - "DSA #5", - 0, - } }, { "ecdsa_5.pub" , -1, -1, 0, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, -1, { NULL, - 21, + 17, HKF_STATUS_OK, 0, NULL, @@ -486,7 +418,7 @@ struct expected expected_full[] = { } }, { "ed25519_5.pub" , -1, -1, 0, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, -1, { NULL, - 22, + 18, HKF_STATUS_OK, 0, NULL, @@ -500,7 +432,7 @@ struct expected expected_full[] = { } }, { "rsa_5.pub" , -1, -1, 0, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, -1, { NULL, - 23, + 19, HKF_STATUS_OK, 0, NULL, @@ -514,7 +446,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 24, + 20, HKF_STATUS_COMMENT, 0, "", @@ -531,51 +463,9 @@ struct expected expected_full[] = { * hostname and addresses in the pre-hashed known_hosts are split * to separate lines. */ - { "dsa_6.pub" , -1, -1, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, 0, -1, { - NULL, - 25, - HKF_STATUS_OK, - 0, - NULL, - MRK_NONE, - NULL, - NULL, - KEY_DSA, - NULL, /* filled at runtime */ - "DSA #6", - 0, - } }, - { "dsa_6.pub" , -1, -1, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, 0, -1, { - NULL, - 26, - HKF_STATUS_OK, - 0, - NULL, - MRK_NONE, - NULL, - NULL, - KEY_DSA, - NULL, /* filled at runtime */ - "DSA #6", - 0, - } }, - { "dsa_6.pub" , -1, -1, 0, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, -1, { - NULL, - 27, - HKF_STATUS_OK, - 0, - NULL, - MRK_NONE, - NULL, - NULL, - KEY_DSA, - NULL, /* filled at runtime */ - "DSA #6", - 0, - } }, { "ecdsa_6.pub" , -1, -1, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, 0, -1, { NULL, - 28, + 21, HKF_STATUS_OK, 0, NULL, @@ -589,7 +479,7 @@ struct expected expected_full[] = { } }, { "ecdsa_6.pub" , -1, -1, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, 0, -1, { NULL, - 29, + 22, HKF_STATUS_OK, 0, NULL, @@ -603,7 +493,7 @@ struct expected expected_full[] = { } }, { "ecdsa_6.pub" , -1, -1, 0, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, -1, { NULL, - 30, + 23, HKF_STATUS_OK, 0, NULL, @@ -617,7 +507,7 @@ struct expected expected_full[] = { } }, { "ed25519_6.pub" , -1, -1, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, 0, -1, { NULL, - 31, + 24, HKF_STATUS_OK, 0, NULL, @@ -631,7 +521,7 @@ struct expected expected_full[] = { } }, { "ed25519_6.pub" , -1, -1, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, 0, -1, { NULL, - 32, + 25, HKF_STATUS_OK, 0, NULL, @@ -645,7 +535,7 @@ struct expected expected_full[] = { } }, { "ed25519_6.pub" , -1, -1, 0, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, -1, { NULL, - 33, + 26, HKF_STATUS_OK, 0, NULL, @@ -659,7 +549,7 @@ struct expected expected_full[] = { } }, { "rsa_6.pub" , -1, -1, HKF_MATCH_HOST|HKF_MATCH_HOST_HASHED, 0, 0, 0, -1, { NULL, - 34, + 27, HKF_STATUS_OK, 0, NULL, @@ -673,7 +563,7 @@ struct expected expected_full[] = { } }, { "rsa_6.pub" , -1, -1, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, 0, -1, { NULL, - 35, + 28, HKF_STATUS_OK, 0, NULL, @@ -687,7 +577,7 @@ struct expected expected_full[] = { } }, { "rsa_6.pub" , -1, -1, 0, 0, 0, HKF_MATCH_IP|HKF_MATCH_IP_HASHED, -1, { NULL, - 36, + 29, HKF_STATUS_OK, 0, NULL, @@ -701,7 +591,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 37, + 30, HKF_STATUS_COMMENT, 0, "", @@ -715,7 +605,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 38, + 31, HKF_STATUS_COMMENT, 0, "", @@ -729,7 +619,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 39, + 32, HKF_STATUS_COMMENT, 0, "# Revoked and CA keys", @@ -743,7 +633,7 @@ struct expected expected_full[] = { } }, { "ed25519_4.pub" , -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, { NULL, - 40, + 33, HKF_STATUS_OK, 0, NULL, @@ -757,7 +647,7 @@ struct expected expected_full[] = { } }, { "ecdsa_4.pub" , -1, -1, HKF_MATCH_HOST, 0, 0, 0, -1, { NULL, - 41, + 34, HKF_STATUS_OK, 0, NULL, @@ -769,23 +659,9 @@ struct expected expected_full[] = { "ECDSA #4", 0, } }, - { "dsa_4.pub" , -1, -1, HKF_MATCH_HOST, HKF_MATCH_HOST, 0, 0, -1, { - NULL, - 42, - HKF_STATUS_OK, - 0, - NULL, - MRK_CA, - "*.example.com", - NULL, - KEY_DSA, - NULL, /* filled at runtime */ - "DSA #4", - 0, - } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 43, + 35, HKF_STATUS_COMMENT, 0, "", @@ -799,7 +675,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 44, + 36, HKF_STATUS_COMMENT, 0, "# Some invalid lines", @@ -813,7 +689,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, 0, 0, 0, -1, { NULL, - 45, + 37, HKF_STATUS_INVALID, 0, NULL, @@ -827,7 +703,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, { NULL, - 46, + 38, HKF_STATUS_INVALID, 0, NULL, @@ -841,7 +717,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, HKF_MATCH_HOST, 0, 0, 0, -1, { NULL, - 47, + 39, HKF_STATUS_INVALID, 0, NULL, @@ -853,9 +729,9 @@ struct expected expected_full[] = { NULL, 0, } }, - { NULL, -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, { + { NULL, HKF_STATUS_OK, KEY_ED25519, 0, HKF_MATCH_HOST, 0, 0, -1, { NULL, - 48, + 40, HKF_STATUS_INVALID, /* Would be ok if key not parsed */ 0, NULL, @@ -869,7 +745,7 @@ struct expected expected_full[] = { } }, { NULL, -1, -1, 0, HKF_MATCH_HOST, 0, 0, -1, { NULL, - 49, + 41, HKF_STATUS_INVALID, 0, NULL, @@ -883,7 +759,7 @@ struct expected expected_full[] = { } }, { NULL, HKF_STATUS_OK, KEY_RSA, HKF_MATCH_HOST, 0, 0, 0, -1, { NULL, - 50, + 42, HKF_STATUS_INVALID, /* Would be ok if key not parsed */ 0, NULL, diff --git a/regress/unittests/hostkeys/testdata/dsa_1.pub b/regress/unittests/hostkeys/testdata/dsa_1.pub deleted file mode 100644 index 56e1e3714625..000000000000 --- a/regress/unittests/hostkeys/testdata/dsa_1.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBAOqffHxEW4c+Z9q/r3l4sYK8F7qrBsU8XF9upGsW62T9InROFFq9IO0x3pQ6mDA0Wtw0sqcDmkPCHPyP4Ok/fU3/drLaZusHoVYu8pBBrWsIDrKgkeX9TEodBsSrYdl4Sqtqq9EZv9+DttV6LStZrgYyUTOKwOF95wGantpLynX5AAAAFQDdt+zjRNlETDsgmxcSYFgREirJrQAAAIBQlrPaiPhR24FhnMLcHH4016vL7AqDDID6Qw7PhbXGa4/XlxWMIigjBKrIPKvnZ6p712LSnCKtcbfdx0MtmJlNa01CYqPaRhgRaf+uGdvTkTUcdaq8R5lLJL+JMNwUhcC8ijm3NqEjXjffuebGe1EzIeiITbA7Nndcd+GytwRDegAAAIEAkRYPjSVcUxfUHhHdpP6V8CuY1+CYSs9EPJ7iiWTDuXWVIBTU32oJLAnrmAcOwtIzEfPvm+rff5FI/Yhon2pB3VTXhPPEBjYzE5qANanAT4e6tzAVc5f3DUhHaDknwRYfDz86GFvuLtDjeE/UZ9t6OofYoEsCBpYozLAprBvNIQY= DSA #1 diff --git a/regress/unittests/hostkeys/testdata/dsa_2.pub b/regress/unittests/hostkeys/testdata/dsa_2.pub deleted file mode 100644 index 394e0bf00255..000000000000 --- a/regress/unittests/hostkeys/testdata/dsa_2.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBAI38Hy/61/O5Bp6yUG8J5XQCeNjRS0xvjlCdzKLyXCueMa+L+X2L/u9PWUsy5SVbTjGgpB8sF6UkCNsV+va7S8zCCHas2MZ7GPlxP6GZBkRPTIFR0N/Pu7wfBzDQz0t0iL4VmxBfTBQv/SxkGWZg+yHihIQP9fwdSAwD/7aVh6ItAAAAFQDSyihIUlINlswM0PJ8wXSti3yIMwAAAIB+oqzaB6ozqs8YxpN5oQOBa/9HEBQEsp8RSIlQmVubXRNgktp42n+Ii1waU9UUk8DX5ahhIeR6B7ojWkqmDAji4SKpoHf4kmr6HvYo85ZSTSx0W4YK/gJHSpDJwhlT52tAfb1JCbWSObjl09B4STv7KedCHcR5oXQvvrV+XoKOSAAAAIAue/EXrs2INw1RfaKNHC0oqOMxmRitv0BFMuNVPo1VDj39CE5kA7AHjwvS1TNeaHtK5Hhgeb6vsmLmNPTOc8xCob0ilyQbt9O0GbONeF2Ge7D2UJyULA/hxql+tCYFIC6yUrmo35fF9XiNisXLoaflk9fjp7ROWWVwnki/jstaQw== DSA #2 diff --git a/regress/unittests/hostkeys/testdata/dsa_3.pub b/regress/unittests/hostkeys/testdata/dsa_3.pub deleted file mode 100644 index e506ea42253a..000000000000 --- a/regress/unittests/hostkeys/testdata/dsa_3.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBAI6lz2Ip9bzE7TGuDD4SjO9S4Ac90gq0h6ai1O06eI8t/Ot2uJ5Jk2QyVr2jvIZHDl/5bwBx7+5oyjlwRoUrAPPD814wf5tU2tSnmdu1Wbf0cBswif5q0r4tevzmopp/AtgH11QHo3u0/pfyJd10qBDLV2FaYSKMmZvyPfZJ0s9pAAAAFQD5Eqjl6Rx2qVePodD9OwAPT0bU6wAAAIAfnDm6csZF0sFaJR3NIJvaYgSGr8s7cqlsk2gLltB/1wOOO2yX+NeEC+B0H93hlMfaUsPa08bwgmYxnavSMqEBpmtPceefJiEd68zwYqXd38f88wyWZ9Z5iwaI/6OVZPHzCbDxOa4ewVTevRNYUKP1xUTZNT8/gSMfZLYPk4T2AQAAAIAUKroozRMyV+3V/rxt0gFnNxRXBKk+9cl3vgsQ7ktkI9cYg7V1T2K0XF21AVMK9gODszy6PBJjV6ruXBV6TRiqIbQauivp3bHHKYsG6wiJNqwdbVwIjfvv8nn1qFoZQLXG3sdONr9NwN8KzrX89OV0BlR2dVM5qqp+YxOXymP9yg== DSA #3 diff --git a/regress/unittests/hostkeys/testdata/dsa_4.pub b/regress/unittests/hostkeys/testdata/dsa_4.pub deleted file mode 100644 index 8552c3819287..000000000000 --- a/regress/unittests/hostkeys/testdata/dsa_4.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBAKvjnFHm0VvMr5h2Zu3nURsxQKGoxm+DCzYDxRYcilK07Cm5c4XTrFbA2X86+9sGs++W7QRMcTJUYIg0a+UtIMtAjwORd6ZPXM2K5dBW+gh1oHyvKi767tWX7I2c+1ZPJDY95mUUfZQUEfdy9eGDSBmw/pSsveQ1ur6XNUh/MtP/AAAAFQDHnXk/9jBJAdce1pHtLWnbdPSGdQAAAIEAm2OLy8tZBfiEO3c3X1yyB/GTcDwrQCqRMDkhnsmrliec3dWkOfNTzu+MrdvF8ymTWLEqPpbMheYtvNyZ3TF0HO5W7aVBpdGZbOdOAIfB+6skqGbI8A5Up1d7dak/bSsqL2r5NjwbDOdq+1hBzzvbl/qjh+sQarV2zHrpKoQaV28AAACANtkBVedBbqIAdphCrN/LbUi9WlyuF9UZz+tlpVLYrj8GJVwnplV2tvOmUw6yP5/pzCimTsao8dpL5PWxm7fKxLWVxA+lEsA4WeC885CiZn8xhdaJOCN+NyJ2bqkz+4VPI7oDGBm0aFwUqJn+M1PiSgvI50XdF2dBsFRTRNY0wzA= DSA #4 diff --git a/regress/unittests/hostkeys/testdata/dsa_5.pub b/regress/unittests/hostkeys/testdata/dsa_5.pub deleted file mode 100644 index 149e1efd166b..000000000000 --- a/regress/unittests/hostkeys/testdata/dsa_5.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBALrFy7w5ihlaOG+qR+6fj+vm5EQaO3qwxgACLcgH+VfShuOG4mkx8qFJmf+OZ3fh5iKngjNZfKtfcqI7zHWdk6378TQfQC52/kbZukjNXOLCpyNkogahcjA00onIoTK1RUDuMW28edAHwPFbpttXDTaqis+8JPMY8hZwsZGENCzTAAAAFQD6+It5vozwGgaN9ROYPMlByhi6jwAAAIBz2mcAC694vNzz9b6614gkX9d9E99PzJYfU1MPkXDziKg7MrjBw7Opd5y1jL09S3iL6lSTlHkKwVKvQ3pOwWRwXXRrKVus4I0STveoApm526jmp6mY0YEtqR98vMJ0v97h1ydt8FikKlihefCsnXVicb8887PXs2Y8C6GuFT3tfQAAAIBbmHtV5tPcrMRDkULhaQ/Whap2VKvT2DUhIHA7lx6oy/KpkltOpxDZOIGUHKqffGbiR7Jh01/y090AY5L2eCf0S2Ytx93+eADwVVpJbFJo6zSwfeey2Gm6L2oA+rCz9zTdmtZoekpD3/RAOQjnJIAPwbs7mXwabZTw4xRtiYIRrw== DSA #5 diff --git a/regress/unittests/hostkeys/testdata/dsa_6.pub b/regress/unittests/hostkeys/testdata/dsa_6.pub deleted file mode 100644 index edbb97643d26..000000000000 --- a/regress/unittests/hostkeys/testdata/dsa_6.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBAIutigAse65TCW6hHDOEGXenE9L4L0talHbs65hj3UUNtWflKdQeXLofqXgW8AwaDKmnuRPrxRoxVNXj84n45wtBEdt4ztmdAZteAbXSnHqpcxME3jDxh3EtxzGPXLs+RUmKPVguraSgo7W2oN7KFx6VM+AcAtxANSTlvDid3s47AAAAFQCd9Q3kkHSLWe77sW0eRaayI45ovwAAAIAw6srGF6xvFasI44Y3r9JJ2K+3ezozl3ldL3p2+p2HG3iWafC4SdV8pB6ZIxKlYAywiiFb3LzH/JweGFq1jtoFDRM3MlYORBevydU4zPz7b5QLDVB0sY4evYtWmg2BFJvoWRfhLnlZVW7h5N8v4fNIwdVmVsw4Ljes7iF2HRGhHgAAAIBDFT3fww2Oby1xUA6G9pDAcVikrQFqp1sJRylNTUyeyQ37SNAGzYxwHJFgQr8gZLdRQ1UW+idYpqVbVNcYFMOiw/zSqK2OfVwPZ9U+TTKdc992ChSup6vJEKM/ZVIyDWDbJr7igQ4ahy7jo9mFvm8ljN926EnspQzCvs0Dxk6tHA== DSA #6 diff --git a/regress/unittests/hostkeys/testdata/known_hosts b/regress/unittests/hostkeys/testdata/known_hosts index 4446f45dffe8..5298e3eebb3d 100644 --- a/regress/unittests/hostkeys/testdata/known_hosts +++ b/regress/unittests/hostkeys/testdata/known_hosts @@ -1,30 +1,23 @@ # Plain host keys, plain host names -sisyphus.example.com ssh-dss AAAAB3NzaC1kc3MAAACBAOqffHxEW4c+Z9q/r3l4sYK8F7qrBsU8XF9upGsW62T9InROFFq9IO0x3pQ6mDA0Wtw0sqcDmkPCHPyP4Ok/fU3/drLaZusHoVYu8pBBrWsIDrKgkeX9TEodBsSrYdl4Sqtqq9EZv9+DttV6LStZrgYyUTOKwOF95wGantpLynX5AAAAFQDdt+zjRNlETDsgmxcSYFgREirJrQAAAIBQlrPaiPhR24FhnMLcHH4016vL7AqDDID6Qw7PhbXGa4/XlxWMIigjBKrIPKvnZ6p712LSnCKtcbfdx0MtmJlNa01CYqPaRhgRaf+uGdvTkTUcdaq8R5lLJL+JMNwUhcC8ijm3NqEjXjffuebGe1EzIeiITbA7Nndcd+GytwRDegAAAIEAkRYPjSVcUxfUHhHdpP6V8CuY1+CYSs9EPJ7iiWTDuXWVIBTU32oJLAnrmAcOwtIzEfPvm+rff5FI/Yhon2pB3VTXhPPEBjYzE5qANanAT4e6tzAVc5f3DUhHaDknwRYfDz86GFvuLtDjeE/UZ9t6OofYoEsCBpYozLAprBvNIQY= DSA #1 sisyphus.example.com ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBF6yQEtD9yBw9gmDRf477WBBzvWhAa0ioBI3nbA4emKykj0RbuQd5C4XdQAEOZGzE7v//FcCjwB2wi+JH5eKkxCtN6CjohDASZ1huoIV2UVyYIicZJEEOg1IWjjphvaxtw== ECDSA #1 sisyphus.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK9ks7jkua5YWIwByRnnnc6UPJQWI75O0e/UJdPYU1JI ED25519 #1 sisyphus.example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDg4hB4vAZHJ0PVRiJajOv/GlytFWNpv5/9xgB9+5BIbvp8LOrFZ5D9K0Gsmwpd4G4rfaAz8j896DhMArg0vtkilIPPGt/6VzWMERgvaIQPJ/IE99X3+fjcAG56oAWwy29JX10lQMzBPU6XJIaN/zqpkb6qUBiAHBdLpxrFBBU0/w== RSA #1 # Plain host keys, hostnames + addresses -prometheus.example.com,192.0.2.1,2001:db8::1 ssh-dss AAAAB3NzaC1kc3MAAACBAI38Hy/61/O5Bp6yUG8J5XQCeNjRS0xvjlCdzKLyXCueMa+L+X2L/u9PWUsy5SVbTjGgpB8sF6UkCNsV+va7S8zCCHas2MZ7GPlxP6GZBkRPTIFR0N/Pu7wfBzDQz0t0iL4VmxBfTBQv/SxkGWZg+yHihIQP9fwdSAwD/7aVh6ItAAAAFQDSyihIUlINlswM0PJ8wXSti3yIMwAAAIB+oqzaB6ozqs8YxpN5oQOBa/9HEBQEsp8RSIlQmVubXRNgktp42n+Ii1waU9UUk8DX5ahhIeR6B7ojWkqmDAji4SKpoHf4kmr6HvYo85ZSTSx0W4YK/gJHSpDJwhlT52tAfb1JCbWSObjl09B4STv7KedCHcR5oXQvvrV+XoKOSAAAAIAue/EXrs2INw1RfaKNHC0oqOMxmRitv0BFMuNVPo1VDj39CE5kA7AHjwvS1TNeaHtK5Hhgeb6vsmLmNPTOc8xCob0ilyQbt9O0GbONeF2Ge7D2UJyULA/hxql+tCYFIC6yUrmo35fF9XiNisXLoaflk9fjp7ROWWVwnki/jstaQw== DSA #2 prometheus.example.com,192.0.2.1,2001:db8::1 ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAB8qVcXwgBM92NCmReQlPrZAoui4Bz/mW0VUBFOpHXXW1n+15b/Y7Pc6UBd/ITTZmaBciXY+PWaSBGdwc5GdqGdLgFyJ/QAGrFMPNpVutm/82gNQzlxpNwjbMcKyiZEXzSgnjS6DzMQ0WuSMdzIBXq8OW/Kafxg4ZkU6YqALUXxlQMZuQ== ECDSA #2 prometheus.example.com,192.0.2.1,2001:db8::1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIBp6PVW0z2o9C4Ukv/JOgmK7QMFe1pD1s3ADFF7IQob ED25519 #2 prometheus.example.com,192.0.2.1,2001:db8::1 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDmbUhNabB5AmBDX6GNHZ3lbn7pRxqfpW+f53QqNGlK0sLV+0gkMIrOfUp1kdE2ZLE6tfzdicatj/RlH6/wuo4yyYb+Pyx3G0vxdmAIiA4aANq38XweDucBC0TZkRWVHK+Gs5V/uV0z7N0axJvkkJujMLvST3CRiiWwlficBc6yVQ== RSA #2 # Some hosts with wildcard names / IPs -*.example.com,192.0.2.*,2001:* ssh-dss AAAAB3NzaC1kc3MAAACBAI6lz2Ip9bzE7TGuDD4SjO9S4Ac90gq0h6ai1O06eI8t/Ot2uJ5Jk2QyVr2jvIZHDl/5bwBx7+5oyjlwRoUrAPPD814wf5tU2tSnmdu1Wbf0cBswif5q0r4tevzmopp/AtgH11QHo3u0/pfyJd10qBDLV2FaYSKMmZvyPfZJ0s9pAAAAFQD5Eqjl6Rx2qVePodD9OwAPT0bU6wAAAIAfnDm6csZF0sFaJR3NIJvaYgSGr8s7cqlsk2gLltB/1wOOO2yX+NeEC+B0H93hlMfaUsPa08bwgmYxnavSMqEBpmtPceefJiEd68zwYqXd38f88wyWZ9Z5iwaI/6OVZPHzCbDxOa4ewVTevRNYUKP1xUTZNT8/gSMfZLYPk4T2AQAAAIAUKroozRMyV+3V/rxt0gFnNxRXBKk+9cl3vgsQ7ktkI9cYg7V1T2K0XF21AVMK9gODszy6PBJjV6ruXBV6TRiqIbQauivp3bHHKYsG6wiJNqwdbVwIjfvv8nn1qFoZQLXG3sdONr9NwN8KzrX89OV0BlR2dVM5qqp+YxOXymP9yg== DSA #3 *.example.com,192.0.2.*,2001:* ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIb3BhJZk+vUQPg5TQc1koIzuGqloCq7wjr9LjlhG24IBeiFHLsdWw74HDlH4DrOmlxToVYk2lTdnjARleRByjk= ECDSA #3 *.example.com,192.0.2.*,2001:* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBlYfExtYZAPqYvYdrlpGlSWhh/XNHcH3v3c2JzsVNbB ED25519 #3 *.example.com,192.0.2.*,2001:* ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDX8F93W3SH4ZSus4XUQ2cw9dqcuyUETTlKEeGv3zlknV3YCoe2Mp04naDhiuwj8sOsytrZSESzLY1ZEyzrjxE6ZFVv8NKgck/AbRjcwlRFOcx9oKUxOrXRa0IoXlTq0kyjKCJfaHBKnGitZThknCPTbVmpATkm5xx6J0WEDozfoQ== RSA #3 # Hashed hostname and address entries -|1|z3xOIdT5ue3Vuf3MzT67kaioqjw=|GZhhe5uwDOBQrC9N4cCjpbLpSn4= ssh-dss AAAAB3NzaC1kc3MAAACBALrFy7w5ihlaOG+qR+6fj+vm5EQaO3qwxgACLcgH+VfShuOG4mkx8qFJmf+OZ3fh5iKngjNZfKtfcqI7zHWdk6378TQfQC52/kbZukjNXOLCpyNkogahcjA00onIoTK1RUDuMW28edAHwPFbpttXDTaqis+8JPMY8hZwsZGENCzTAAAAFQD6+It5vozwGgaN9ROYPMlByhi6jwAAAIBz2mcAC694vNzz9b6614gkX9d9E99PzJYfU1MPkXDziKg7MrjBw7Opd5y1jL09S3iL6lSTlHkKwVKvQ3pOwWRwXXRrKVus4I0STveoApm526jmp6mY0YEtqR98vMJ0v97h1ydt8FikKlihefCsnXVicb8887PXs2Y8C6GuFT3tfQAAAIBbmHtV5tPcrMRDkULhaQ/Whap2VKvT2DUhIHA7lx6oy/KpkltOpxDZOIGUHKqffGbiR7Jh01/y090AY5L2eCf0S2Ytx93+eADwVVpJbFJo6zSwfeey2Gm6L2oA+rCz9zTdmtZoekpD3/RAOQjnJIAPwbs7mXwabZTw4xRtiYIRrw== DSA #5 |1|B7t/AYabn8zgwU47Cb4A/Nqt3eI=|arQPZyRphkzisr7w6wwikvhaOyE= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIudcagzq4QPtP1jkpje34+0POLB0jwT64hqrbCqhTH2T800KDZ0h2vwlJYa3OP3Oqru9AB5pnuHsKw7mAhUGY= ECDSA #5 |1|JR81WxEocTP5d7goIRkl8fHBbno=|l6sj6FOsoXxgEZMzn/BnOfPKN68= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINf63qSV8rD57N+digID8t28WVhd3Yf2K2UhaoG8TsWQ ED25519 #5 |1|W7x4zY6KtTZJgsopyOusJqvVPag=|QauLt7hKezBZFZi2i4Xopho7Nsk= ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC/C15Q4sfnk7BZff1er8bscay+5s51oD4eWArlHWMK/ZfYeeTAccTy+7B7Jv+MS4nKCpflrvJI2RQz4kS8vF0ATdBbi4jeWefStlHNg0HLhnCY7NAfDIlRdaN9lm3Pqm2vmr+CkqwcJaSpycDg8nPN9yNAuD6pv7NDuUnECezojQ== RSA #5 -|1|mxnU8luzqWLvfVi5qBm5xVIyCRM=|9Epopft7LBd80Bf6RmWPIpwa8yU= ssh-dss AAAAB3NzaC1kc3MAAACBAIutigAse65TCW6hHDOEGXenE9L4L0talHbs65hj3UUNtWflKdQeXLofqXgW8AwaDKmnuRPrxRoxVNXj84n45wtBEdt4ztmdAZteAbXSnHqpcxME3jDxh3EtxzGPXLs+RUmKPVguraSgo7W2oN7KFx6VM+AcAtxANSTlvDid3s47AAAAFQCd9Q3kkHSLWe77sW0eRaayI45ovwAAAIAw6srGF6xvFasI44Y3r9JJ2K+3ezozl3ldL3p2+p2HG3iWafC4SdV8pB6ZIxKlYAywiiFb3LzH/JweGFq1jtoFDRM3MlYORBevydU4zPz7b5QLDVB0sY4evYtWmg2BFJvoWRfhLnlZVW7h5N8v4fNIwdVmVsw4Ljes7iF2HRGhHgAAAIBDFT3fww2Oby1xUA6G9pDAcVikrQFqp1sJRylNTUyeyQ37SNAGzYxwHJFgQr8gZLdRQ1UW+idYpqVbVNcYFMOiw/zSqK2OfVwPZ9U+TTKdc992ChSup6vJEKM/ZVIyDWDbJr7igQ4ahy7jo9mFvm8ljN926EnspQzCvs0Dxk6tHA== DSA #6 -|1|klvLmvh2vCpkNMDEjVvrE8SJWTg=|e/dqEEBLnbgqmwEesl4cDRu/7TM= ssh-dss AAAAB3NzaC1kc3MAAACBAIutigAse65TCW6hHDOEGXenE9L4L0talHbs65hj3UUNtWflKdQeXLofqXgW8AwaDKmnuRPrxRoxVNXj84n45wtBEdt4ztmdAZteAbXSnHqpcxME3jDxh3EtxzGPXLs+RUmKPVguraSgo7W2oN7KFx6VM+AcAtxANSTlvDid3s47AAAAFQCd9Q3kkHSLWe77sW0eRaayI45ovwAAAIAw6srGF6xvFasI44Y3r9JJ2K+3ezozl3ldL3p2+p2HG3iWafC4SdV8pB6ZIxKlYAywiiFb3LzH/JweGFq1jtoFDRM3MlYORBevydU4zPz7b5QLDVB0sY4evYtWmg2BFJvoWRfhLnlZVW7h5N8v4fNIwdVmVsw4Ljes7iF2HRGhHgAAAIBDFT3fww2Oby1xUA6G9pDAcVikrQFqp1sJRylNTUyeyQ37SNAGzYxwHJFgQr8gZLdRQ1UW+idYpqVbVNcYFMOiw/zSqK2OfVwPZ9U+TTKdc992ChSup6vJEKM/ZVIyDWDbJr7igQ4ahy7jo9mFvm8ljN926EnspQzCvs0Dxk6tHA== DSA #6 -|1|wsk3ddB3UjuxEsoeNCeZjZ6NvZs=|O3O/q2Z/u7DrxoTiIq6kzCevQT0= ssh-dss AAAAB3NzaC1kc3MAAACBAIutigAse65TCW6hHDOEGXenE9L4L0talHbs65hj3UUNtWflKdQeXLofqXgW8AwaDKmnuRPrxRoxVNXj84n45wtBEdt4ztmdAZteAbXSnHqpcxME3jDxh3EtxzGPXLs+RUmKPVguraSgo7W2oN7KFx6VM+AcAtxANSTlvDid3s47AAAAFQCd9Q3kkHSLWe77sW0eRaayI45ovwAAAIAw6srGF6xvFasI44Y3r9JJ2K+3ezozl3ldL3p2+p2HG3iWafC4SdV8pB6ZIxKlYAywiiFb3LzH/JweGFq1jtoFDRM3MlYORBevydU4zPz7b5QLDVB0sY4evYtWmg2BFJvoWRfhLnlZVW7h5N8v4fNIwdVmVsw4Ljes7iF2HRGhHgAAAIBDFT3fww2Oby1xUA6G9pDAcVikrQFqp1sJRylNTUyeyQ37SNAGzYxwHJFgQr8gZLdRQ1UW+idYpqVbVNcYFMOiw/zSqK2OfVwPZ9U+TTKdc992ChSup6vJEKM/ZVIyDWDbJr7igQ4ahy7jo9mFvm8ljN926EnspQzCvs0Dxk6tHA== DSA #6 |1|B8epmkLSni+vGZDijr/EwxeR2k4=|7ct8yzNOVJhKm3ZD2w0XIT7df8E= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK1wRLyKtvK3Mmhd0XPkKwW4ev1KBVf8J4aG8lESq1TsaqqfOXYGyxMq5pN8fCGiD5UPOqyTYz/ZNzClRhJRHao= ECDSA #6 |1|JojD885UhYhbCu571rgyM/5PpYU=|BJaU2aE1FebQZy3B5tzTDRWFRG0= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK1wRLyKtvK3Mmhd0XPkKwW4ev1KBVf8J4aG8lESq1TsaqqfOXYGyxMq5pN8fCGiD5UPOqyTYz/ZNzClRhJRHao= ECDSA #6 |1|5t7UDHDybVrDZVQPCpwdnr6nk4k=|EqJ73W/veIL3H2x+YWHcJxI5ETA= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK1wRLyKtvK3Mmhd0XPkKwW4ev1KBVf8J4aG8lESq1TsaqqfOXYGyxMq5pN8fCGiD5UPOqyTYz/ZNzClRhJRHao= ECDSA #6 @@ -39,12 +32,11 @@ prometheus.example.com,192.0.2.1,2001:db8::1 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAA # Revoked and CA keys @revoked sisyphus.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFP8L9REfN/iYy1KIRtFqSCn3V2+vOCpoZYENFGLdOF ED25519 #4 @cert-authority prometheus.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHZd0OXHIWwK3xnjAdMZ1tojxWycdu38pORO/UX5cqsKMgGCKQVBWWO3TFk1ePkGIE9VMWT1hCGqWRRwYlH+dSE= ECDSA #4 -@cert-authority *.example.com ssh-dss AAAAB3NzaC1kc3MAAACBAKvjnFHm0VvMr5h2Zu3nURsxQKGoxm+DCzYDxRYcilK07Cm5c4XTrFbA2X86+9sGs++W7QRMcTJUYIg0a+UtIMtAjwORd6ZPXM2K5dBW+gh1oHyvKi767tWX7I2c+1ZPJDY95mUUfZQUEfdy9eGDSBmw/pSsveQ1ur6XNUh/MtP/AAAAFQDHnXk/9jBJAdce1pHtLWnbdPSGdQAAAIEAm2OLy8tZBfiEO3c3X1yyB/GTcDwrQCqRMDkhnsmrliec3dWkOfNTzu+MrdvF8ymTWLEqPpbMheYtvNyZ3TF0HO5W7aVBpdGZbOdOAIfB+6skqGbI8A5Up1d7dak/bSsqL2r5NjwbDOdq+1hBzzvbl/qjh+sQarV2zHrpKoQaV28AAACANtkBVedBbqIAdphCrN/LbUi9WlyuF9UZz+tlpVLYrj8GJVwnplV2tvOmUw6yP5/pzCimTsao8dpL5PWxm7fKxLWVxA+lEsA4WeC885CiZn8xhdaJOCN+NyJ2bqkz+4VPI7oDGBm0aFwUqJn+M1PiSgvI50XdF2dBsFRTRNY0wzA= DSA #4 # Some invalid lines -@what sisyphus.example.com ssh-dss AAAAB3NzaC1kc3MAAACBAOqffHxEW4c+Z9q/r3l4sYK8F7qrBsU8XF9upGsW62T9InROFFq9IO0x3pQ6mDA0Wtw0sqcDmkPCHPyP4Ok/fU3/drLaZusHoVYu8pBBrWsIDrKgkeX9TEodBsSrYdl4Sqtqq9EZv9+DttV6LStZrgYyUTOKwOF95wGantpLynX5AAAAFQDdt+zjRNlETDsgmxcSYFgREirJrQAAAIBQlrPaiPhR24FhnMLcHH4016vL7AqDDID6Qw7PhbXGa4/XlxWMIigjBKrIPKvnZ6p712LSnCKtcbfdx0MtmJlNa01CYqPaRhgRaf+uGdvTkTUcdaq8R5lLJL+JMNwUhcC8ijm3NqEjXjffuebGe1EzIeiITbA7Nndcd+GytwRDegAAAIEAkRYPjSVcUxfUHhHdpP6V8CuY1+CYSs9EPJ7iiWTDuXWVIBTU32oJLAnrmAcOwtIzEfPvm+rff5FI/Yhon2pB3VTXhPPEBjYzE5qANanAT4e6tzAVc5f3DUhHaDknwRYfDz86GFvuLtDjeE/UZ9t6OofYoEsCBpYozLAprBvNIQY= DSA #1 +@what ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDg4hB4vAZHJ0PVRiJajOv/GlytFWNpv5/9xgB9+5BIbvp8LOrFZ5D9K0Gsmwpd4G4rfaAz8j896DhMArg0vtkilIPPGt/6VzWMERgvaIQPJ/IE99X3+fjcAG56oAWwy29JX10lQMzBPU6XJIaN/zqpkb6qUBiAHBdLpxrFBBU0/w== RSA #1 sisyphus.example.com prometheus.example.com ssh-ed25519 -sisyphus.example.com ssh-dsa AAAATgAAAAdz +sisyphus.example.com ssh-ed25519 AAAATgAAAAdz sisyphus.example.com ssh-XXX AAAATgAAAAdzc2gtWFhYAAAAP0ZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRg== prometheus.example.com ssh-rsa AAAATgAAAAdzc2gtWFhYAAAAP0ZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRkZVQ0tPRkZGVUNLT0ZGRlVDS09GRg== diff --git a/regress/unittests/kex/Makefile b/regress/unittests/kex/Makefile index b76ee8edc813..645fb0609733 100644 --- a/regress/unittests/kex/Makefile +++ b/regress/unittests/kex/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.17 2025/04/15 04:00:42 djm Exp $ +# $OpenBSD: Makefile,v 1.18 2025/05/06 06:05:48 djm Exp $ PROG=test_kex SRCS=tests.c test_kex.c test_proposal.c @@ -6,7 +6,7 @@ SRCS=tests.c test_kex.c test_proposal.c # From usr.bin/ssh SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c -SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c +SRCS+=ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c SRCS+=addr.c addrmatch.c bitmap.c packet.c dispatch.c canohost.c ssh_api.c SRCS+=compat.c ed25519.c hash.c diff --git a/regress/unittests/kex/test_kex.c b/regress/unittests/kex/test_kex.c index 84dada301b8f..54b826239ae8 100644 --- a/regress/unittests/kex/test_kex.c +++ b/regress/unittests/kex/test_kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_kex.c,v 1.10 2025/04/15 04:00:42 djm Exp $ */ +/* $OpenBSD: test_kex.c,v 1.11 2025/05/06 06:05:48 djm Exp $ */ /* * Regress test KEX * @@ -218,9 +218,6 @@ do_kex(char *kex) #ifdef WITH_OPENSSL do_kex_with_key(kex, NULL, NULL, NULL, KEY_RSA, 2048); -# ifdef WITH_DSA - do_kex_with_key(kex, NULL, NULL, NULL, KEY_DSA, 1024); -# endif /* WITH_DSA */ # ifdef OPENSSL_HAS_ECC do_kex_with_key(kex, NULL, NULL, NULL, KEY_ECDSA, 256); # endif /* OPENSSL_HAS_ECC */ diff --git a/regress/unittests/sshkey/Makefile b/regress/unittests/sshkey/Makefile index cd0f44d13d24..b237ff55c8d3 100644 --- a/regress/unittests/sshkey/Makefile +++ b/regress/unittests/sshkey/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.12 2023/01/15 23:35:10 djm Exp $ +# $OpenBSD: Makefile,v 1.13 2025/05/06 06:05:48 djm Exp $ PROG=test_sshkey SRCS=tests.c test_sshkey.c test_file.c test_fuzz.c common.c @@ -6,7 +6,7 @@ SRCS=tests.c test_sshkey.c test_file.c test_fuzz.c common.c # From usr.bin/ssh SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c -SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c +SRCS+=ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c SRCS+=addr.c addrmatch.c bitmap.c SRCS+=ed25519.c hash.c diff --git a/regress/unittests/sshkey/common.c b/regress/unittests/sshkey/common.c index f325c2ac2025..a579eccb29d5 100644 --- a/regress/unittests/sshkey/common.c +++ b/regress/unittests/sshkey/common.c @@ -1,4 +1,4 @@ -/* $OpenBSD: common.c,v 1.6 2024/08/15 00:52:23 djm Exp $ */ +/* $OpenBSD: common.c,v 1.7 2025/05/06 06:05:48 djm Exp $ */ /* * Helpers for key API tests * @@ -21,7 +21,6 @@ #ifdef WITH_OPENSSL #include #include -#include #include #ifdef OPENSSL_HAS_NISTP256 # include @@ -126,38 +125,4 @@ rsa_q(struct sshkey *k) RSA_get0_factors(EVP_PKEY_get0_RSA(k->pkey), NULL, &q); return q; } - -const BIGNUM * -dsa_g(struct sshkey *k) -{ - const BIGNUM *g = NULL; - - ASSERT_PTR_NE(k, NULL); - ASSERT_PTR_NE(k->dsa, NULL); - DSA_get0_pqg(k->dsa, NULL, NULL, &g); - return g; -} - -const BIGNUM * -dsa_pub_key(struct sshkey *k) -{ - const BIGNUM *pub_key = NULL; - - ASSERT_PTR_NE(k, NULL); - ASSERT_PTR_NE(k->dsa, NULL); - DSA_get0_key(k->dsa, &pub_key, NULL); - return pub_key; -} - -const BIGNUM * -dsa_priv_key(struct sshkey *k) -{ - const BIGNUM *priv_key = NULL; - - ASSERT_PTR_NE(k, NULL); - ASSERT_PTR_NE(k->dsa, NULL); - DSA_get0_key(k->dsa, NULL, &priv_key); - return priv_key; -} #endif /* WITH_OPENSSL */ - diff --git a/regress/unittests/sshkey/common.h b/regress/unittests/sshkey/common.h index 7a514fdc8fe6..6127116da3d4 100644 --- a/regress/unittests/sshkey/common.h +++ b/regress/unittests/sshkey/common.h @@ -1,4 +1,4 @@ -/* $OpenBSD: common.h,v 1.2 2018/09/13 09:03:20 djm Exp $ */ +/* $OpenBSD: common.h,v 1.3 2025/05/06 06:05:48 djm Exp $ */ /* * Helpers for key API tests * @@ -19,7 +19,4 @@ const BIGNUM *rsa_n(struct sshkey *k); const BIGNUM *rsa_e(struct sshkey *k); const BIGNUM *rsa_p(struct sshkey *k); const BIGNUM *rsa_q(struct sshkey *k); -const BIGNUM *dsa_g(struct sshkey *k); -const BIGNUM *dsa_pub_key(struct sshkey *k); -const BIGNUM *dsa_priv_key(struct sshkey *k); diff --git a/regress/unittests/sshkey/mktestdata.sh b/regress/unittests/sshkey/mktestdata.sh index fcd78e990e8b..97e5d79fd734 100755 --- a/regress/unittests/sshkey/mktestdata.sh +++ b/regress/unittests/sshkey/mktestdata.sh @@ -1,5 +1,5 @@ #!/bin/sh -# $OpenBSD: mktestdata.sh,v 1.11 2020/06/19 03:48:49 djm Exp $ +# $OpenBSD: mktestdata.sh,v 1.12 2025/05/06 06:05:48 djm Exp $ PW=mekmitasdigoat @@ -24,27 +24,6 @@ rsa_params() { done } -dsa_params() { - _in="$1" - _outbase="$2" - set -e - openssl dsa -noout -text -in $_in | \ - awk '/^priv:$/,/^pub:/' | \ - grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.priv - openssl dsa -noout -text -in $_in | \ - awk '/^pub:/,/^P:/' | #\ - grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.pub - openssl dsa -noout -text -in $_in | \ - awk '/^G:/,0' | \ - grep -v '^[a-zA-Z]' | tr -d ' \n:' > ${_outbase}.g - for x in priv pub g ; do - echo "" >> ${_outbase}.$x - echo ============ ${_outbase}.$x - cat ${_outbase}.$x - echo ============ - done -} - ecdsa_params() { _in="$1" _outbase="$2" @@ -79,15 +58,14 @@ else exit 1 fi -rm -f rsa_1 dsa_1 ecdsa_1 ed25519_1 -rm -f rsa_2 dsa_2 ecdsa_2 ed25519_2 -rm -f rsa_n dsa_n ecdsa_n # new-format keys -rm -f rsa_1_pw dsa_1_pw ecdsa_1_pw ed25519_1_pw -rm -f rsa_n_pw dsa_n_pw ecdsa_n_pw +rm -f rsa_1 ecdsa_1 ed25519_1 +rm -f rsa_2 ecdsa_2 ed25519_2 +rm -f rsa_n ecdsa_n # new-format keys +rm -f rsa_1_pw ecdsa_1_pw ed25519_1_pw +rm -f rsa_n_pw ecdsa_n_pw rm -f pw *.pub *.bn.* *.param.* *.fp *.fp.bb ssh-keygen -t rsa -b 1024 -C "RSA test key #1" -N "" -f rsa_1 -m PEM -ssh-keygen -t dsa -b 1024 -C "DSA test key #1" -N "" -f dsa_1 -m PEM ssh-keygen -t ecdsa -b 256 -C "ECDSA test key #1" -N "" -f ecdsa_1 -m PEM ssh-keygen -t ed25519 -C "ED25519 test key #1" -N "" -f ed25519_1 ssh-keygen -w "$SK_DUMMY" -t ecdsa-sk -C "ECDSA-SK test key #1" \ @@ -97,7 +75,6 @@ ssh-keygen -w "$SK_DUMMY" -t ed25519-sk -C "ED25519-SK test key #1" \ ssh-keygen -t rsa -b 2048 -C "RSA test key #2" -N "" -f rsa_2 -m PEM -ssh-keygen -t dsa -b 1024 -C "DSA test key #2" -N "" -f dsa_2 -m PEM ssh-keygen -t ecdsa -b 521 -C "ECDSA test key #2" -N "" -f ecdsa_2 -m PEM ssh-keygen -t ed25519 -C "ED25519 test key #2" -N "" -f ed25519_2 ssh-keygen -w "$SK_DUMMY" -t ecdsa-sk -C "ECDSA-SK test key #2" \ @@ -106,37 +83,29 @@ ssh-keygen -w "$SK_DUMMY" -t ed25519-sk -C "ED25519-SK test key #2" \ -N "" -f ed25519_sk2 cp rsa_1 rsa_n -cp dsa_1 dsa_n cp ecdsa_1 ecdsa_n ssh-keygen -pf rsa_n -N "" -ssh-keygen -pf dsa_n -N "" ssh-keygen -pf ecdsa_n -N "" cp rsa_1 rsa_1_pw -cp dsa_1 dsa_1_pw cp ecdsa_1 ecdsa_1_pw cp ed25519_1 ed25519_1_pw cp ecdsa_sk1 ecdsa_sk1_pw cp ed25519_sk1 ed25519_sk1_pw cp rsa_1 rsa_n_pw -cp dsa_1 dsa_n_pw cp ecdsa_1 ecdsa_n_pw ssh-keygen -pf rsa_1_pw -m PEM -N "$PW" -ssh-keygen -pf dsa_1_pw -m PEM -N "$PW" ssh-keygen -pf ecdsa_1_pw -m PEM -N "$PW" ssh-keygen -pf ed25519_1_pw -N "$PW" ssh-keygen -pf ecdsa_sk1_pw -m PEM -N "$PW" ssh-keygen -pf ed25519_sk1_pw -N "$PW" ssh-keygen -pf rsa_n_pw -N "$PW" -ssh-keygen -pf dsa_n_pw -N "$PW" ssh-keygen -pf ecdsa_n_pw -N "$PW" rsa_params rsa_1 rsa_1.param rsa_params rsa_2 rsa_2.param -dsa_params dsa_1 dsa_1.param -dsa_params dsa_1 dsa_1.param ecdsa_params ecdsa_1 ecdsa_1.param ecdsa_params ecdsa_2 ecdsa_2.param # XXX ed25519, *sk params @@ -144,9 +113,6 @@ ecdsa_params ecdsa_2 ecdsa_2.param ssh-keygen -s rsa_2 -I hugo -n user1,user2 \ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \ -V 19990101:20110101 -z 1 rsa_1.pub -ssh-keygen -s rsa_2 -I hugo -n user1,user2 \ - -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \ - -V 19990101:20110101 -z 2 dsa_1.pub ssh-keygen -s rsa_2 -I hugo -n user1,user2 \ -Oforce-command=/bin/ls -Ono-port-forwarding -Osource-address=10.0.0.0/8 \ -V 19990101:20110101 -z 3 ecdsa_1.pub @@ -175,8 +141,6 @@ ssh-keygen -s rsa_2 -I hugo -n user1,user2 -t rsa-sha2-512 \ ssh-keygen -s ed25519_1 -I julius -n host1,host2 -h \ -V 19990101:20110101 -z 5 rsa_1.pub -ssh-keygen -s ed25519_1 -I julius -n host1,host2 -h \ - -V 19990101:20110101 -z 6 dsa_1.pub ssh-keygen -s ecdsa_1 -I julius -n host1,host2 -h \ -V 19990101:20110101 -z 7 ecdsa_1.pub ssh-keygen -s ed25519_1 -I julius -n host1,host2 -h \ @@ -187,33 +151,28 @@ ssh-keygen -s ed25519_1 -I julius -n host1,host2 -h \ -V 19990101:20110101 -z 8 ed25519_sk1.pub ssh-keygen -lf rsa_1 | awk '{print $2}' > rsa_1.fp -ssh-keygen -lf dsa_1 | awk '{print $2}' > dsa_1.fp ssh-keygen -lf ecdsa_1 | awk '{print $2}' > ecdsa_1.fp ssh-keygen -lf ed25519_1 | awk '{print $2}' > ed25519_1.fp ssh-keygen -lf ecdsa_sk1 | awk '{print $2}' > ecdsa_sk1.fp ssh-keygen -lf ed25519_sk1 | awk '{print $2}' > ed25519_sk1.fp ssh-keygen -lf rsa_2 | awk '{print $2}' > rsa_2.fp -ssh-keygen -lf dsa_2 | awk '{print $2}' > dsa_2.fp ssh-keygen -lf ecdsa_2 | awk '{print $2}' > ecdsa_2.fp ssh-keygen -lf ed25519_2 | awk '{print $2}' > ed25519_2.fp ssh-keygen -lf ecdsa_sk2 | awk '{print $2}' > ecdsa_sk2.fp ssh-keygen -lf ed25519_sk2 | awk '{print $2}' > ed25519_sk2.fp ssh-keygen -lf rsa_1-cert.pub | awk '{print $2}' > rsa_1-cert.fp -ssh-keygen -lf dsa_1-cert.pub | awk '{print $2}' > dsa_1-cert.fp ssh-keygen -lf ecdsa_1-cert.pub | awk '{print $2}' > ecdsa_1-cert.fp ssh-keygen -lf ed25519_1-cert.pub | awk '{print $2}' > ed25519_1-cert.fp ssh-keygen -lf ecdsa_sk1-cert.pub | awk '{print $2}' > ecdsa_sk1-cert.fp ssh-keygen -lf ed25519_sk1-cert.pub | awk '{print $2}' > ed25519_sk1-cert.fp ssh-keygen -Bf rsa_1 | awk '{print $2}' > rsa_1.fp.bb -ssh-keygen -Bf dsa_1 | awk '{print $2}' > dsa_1.fp.bb ssh-keygen -Bf ecdsa_1 | awk '{print $2}' > ecdsa_1.fp.bb ssh-keygen -Bf ed25519_1 | awk '{print $2}' > ed25519_1.fp.bb ssh-keygen -Bf ecdsa_sk1 | awk '{print $2}' > ecdsa_sk1.fp.bb ssh-keygen -Bf ed25519_sk1 | awk '{print $2}' > ed25519_sk1.fp.bb ssh-keygen -Bf rsa_2 | awk '{print $2}' > rsa_2.fp.bb -ssh-keygen -Bf dsa_2 | awk '{print $2}' > dsa_2.fp.bb ssh-keygen -Bf ecdsa_2 | awk '{print $2}' > ecdsa_2.fp.bb ssh-keygen -Bf ed25519_2 | awk '{print $2}' > ed25519_2.fp.bb ssh-keygen -Bf ecdsa_sk2 | awk '{print $2}' > ecdsa_sk2.fp.bb diff --git a/regress/unittests/sshkey/test_file.c b/regress/unittests/sshkey/test_file.c index 3babe604dcca..49148aca07b2 100644 --- a/regress/unittests/sshkey/test_file.c +++ b/regress/unittests/sshkey/test_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_file.c,v 1.12 2024/08/15 00:52:23 djm Exp $ */ +/* $OpenBSD: test_file.c,v 1.13 2025/05/06 06:05:48 djm Exp $ */ /* * Regress test for sshkey.h key management API * @@ -21,7 +21,6 @@ #ifdef WITH_OPENSSL #include #include -#include #include #ifdef OPENSSL_HAS_NISTP256 # include @@ -165,99 +164,6 @@ sshkey_file_tests(void) sshkey_free(k1); -#ifdef WITH_DSA - TEST_START("parse DSA from private"); - buf = load_file("dsa_1"); - ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0); - sshbuf_free(buf); - ASSERT_PTR_NE(k1, NULL); - a = load_bignum("dsa_1.param.g"); - b = load_bignum("dsa_1.param.priv"); - c = load_bignum("dsa_1.param.pub"); - ASSERT_BIGNUM_EQ(dsa_g(k1), a); - ASSERT_BIGNUM_EQ(dsa_priv_key(k1), b); - ASSERT_BIGNUM_EQ(dsa_pub_key(k1), c); - BN_free(a); - BN_free(b); - BN_free(c); - TEST_DONE(); - - TEST_START("parse DSA from private w/ passphrase"); - buf = load_file("dsa_1_pw"); - ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, - (const char *)sshbuf_ptr(pw), &k2, NULL), 0); - sshbuf_free(buf); - ASSERT_PTR_NE(k2, NULL); - ASSERT_INT_EQ(sshkey_equal(k1, k2), 1); - sshkey_free(k2); - TEST_DONE(); - - TEST_START("parse DSA from new-format"); - buf = load_file("dsa_n"); - ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k2, NULL), 0); - sshbuf_free(buf); - ASSERT_PTR_NE(k2, NULL); - ASSERT_INT_EQ(sshkey_equal(k1, k2), 1); - sshkey_free(k2); - TEST_DONE(); - - TEST_START("parse DSA from new-format w/ passphrase"); - buf = load_file("dsa_n_pw"); - ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, - (const char *)sshbuf_ptr(pw), &k2, NULL), 0); - sshbuf_free(buf); - ASSERT_PTR_NE(k2, NULL); - ASSERT_INT_EQ(sshkey_equal(k1, k2), 1); - sshkey_free(k2); - TEST_DONE(); - - TEST_START("load DSA from public"); - ASSERT_INT_EQ(sshkey_load_public(test_data_file("dsa_1.pub"), &k2, - NULL), 0); - ASSERT_PTR_NE(k2, NULL); - ASSERT_INT_EQ(sshkey_equal(k1, k2), 1); - sshkey_free(k2); - TEST_DONE(); - - TEST_START("load DSA cert"); - ASSERT_INT_EQ(sshkey_load_cert(test_data_file("dsa_1"), &k2), 0); - ASSERT_PTR_NE(k2, NULL); - ASSERT_INT_EQ(k2->type, KEY_DSA_CERT); - ASSERT_INT_EQ(sshkey_equal(k1, k2), 0); - ASSERT_INT_EQ(sshkey_equal_public(k1, k2), 1); - TEST_DONE(); - - TEST_START("DSA key hex fingerprint"); - buf = load_text_file("dsa_1.fp"); - cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA256, SSH_FP_BASE64); - ASSERT_PTR_NE(cp, NULL); - ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf)); - sshbuf_free(buf); - free(cp); - TEST_DONE(); - - TEST_START("DSA cert hex fingerprint"); - buf = load_text_file("dsa_1-cert.fp"); - cp = sshkey_fingerprint(k2, SSH_DIGEST_SHA256, SSH_FP_BASE64); - ASSERT_PTR_NE(cp, NULL); - ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf)); - sshbuf_free(buf); - free(cp); - sshkey_free(k2); - TEST_DONE(); - - TEST_START("DSA key bubblebabble fingerprint"); - buf = load_text_file("dsa_1.fp.bb"); - cp = sshkey_fingerprint(k1, SSH_DIGEST_SHA1, SSH_FP_BUBBLEBABBLE); - ASSERT_PTR_NE(cp, NULL); - ASSERT_STRING_EQ(cp, (const char *)sshbuf_ptr(buf)); - sshbuf_free(buf); - free(cp); - TEST_DONE(); - - sshkey_free(k1); -#endif - #ifdef OPENSSL_HAS_ECC TEST_START("parse ECDSA from private"); buf = load_file("ecdsa_1"); diff --git a/regress/unittests/sshkey/test_fuzz.c b/regress/unittests/sshkey/test_fuzz.c index 0aff7c9bf4e4..12d0e12eacef 100644 --- a/regress/unittests/sshkey/test_fuzz.c +++ b/regress/unittests/sshkey/test_fuzz.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_fuzz.c,v 1.14 2024/01/11 01:45:58 djm Exp $ */ +/* $OpenBSD: test_fuzz.c,v 1.15 2025/05/06 06:05:48 djm Exp $ */ /* * Fuzz tests for key parsing * @@ -21,7 +21,6 @@ #ifdef WITH_OPENSSL #include #include -#include #include #ifdef OPENSSL_HAS_NISTP256 # include @@ -160,52 +159,6 @@ sshkey_fuzz_tests(void) fuzz_cleanup(fuzz); TEST_DONE(); -#ifdef WITH_DSA - TEST_START("fuzz DSA private"); - buf = load_file("dsa_1"); - fuzz = fuzz_begin(FUZZ_BASE64, sshbuf_mutable_ptr(buf), - sshbuf_len(buf)); - ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0); - sshkey_free(k1); - sshbuf_free(buf); - ASSERT_PTR_NE(fuzzed = sshbuf_new(), NULL); - TEST_ONERROR(onerror, fuzz); - for(i = 0; !fuzz_done(fuzz); i++, fuzz_next(fuzz)) { - r = sshbuf_put(fuzzed, fuzz_ptr(fuzz), fuzz_len(fuzz)); - ASSERT_INT_EQ(r, 0); - if (sshkey_parse_private_fileblob(fuzzed, "", &k1, NULL) == 0) - sshkey_free(k1); - sshbuf_reset(fuzzed); - if (test_is_fast() && i >= NUM_FAST_BASE64_TESTS) - break; - } - sshbuf_free(fuzzed); - fuzz_cleanup(fuzz); - TEST_DONE(); - - TEST_START("fuzz DSA new-format private"); - buf = load_file("dsa_n"); - fuzz = fuzz_begin(FUZZ_BASE64, sshbuf_mutable_ptr(buf), - sshbuf_len(buf)); - ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0); - sshkey_free(k1); - sshbuf_free(buf); - ASSERT_PTR_NE(fuzzed = sshbuf_new(), NULL); - TEST_ONERROR(onerror, fuzz); - for(i = 0; !fuzz_done(fuzz); i++, fuzz_next(fuzz)) { - r = sshbuf_put(fuzzed, fuzz_ptr(fuzz), fuzz_len(fuzz)); - ASSERT_INT_EQ(r, 0); - if (sshkey_parse_private_fileblob(fuzzed, "", &k1, NULL) == 0) - sshkey_free(k1); - sshbuf_reset(fuzzed); - if (test_is_fast() && i >= NUM_FAST_BASE64_TESTS) - break; - } - sshbuf_free(fuzzed); - fuzz_cleanup(fuzz); - TEST_DONE(); -#endif - #ifdef OPENSSL_HAS_ECC TEST_START("fuzz ECDSA private"); buf = load_file("ecdsa_1"); @@ -290,22 +243,6 @@ sshkey_fuzz_tests(void) sshkey_free(k1); TEST_DONE(); -#ifdef WITH_DSA - TEST_START("fuzz DSA public"); - buf = load_file("dsa_1"); - ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0); - sshbuf_free(buf); - public_fuzz(k1); - sshkey_free(k1); - TEST_DONE(); - - TEST_START("fuzz DSA cert"); - ASSERT_INT_EQ(sshkey_load_cert(test_data_file("dsa_1"), &k1), 0); - public_fuzz(k1); - sshkey_free(k1); - TEST_DONE(); -#endif - #ifdef OPENSSL_HAS_ECC TEST_START("fuzz ECDSA public"); buf = load_file("ecdsa_1"); @@ -362,16 +299,6 @@ sshkey_fuzz_tests(void) sshkey_free(k1); TEST_DONE(); -#ifdef WITH_DSA - TEST_START("fuzz DSA sig"); - buf = load_file("dsa_1"); - ASSERT_INT_EQ(sshkey_parse_private_fileblob(buf, "", &k1, NULL), 0); - sshbuf_free(buf); - sig_fuzz(k1, NULL); - sshkey_free(k1); - TEST_DONE(); -#endif - #ifdef OPENSSL_HAS_ECC TEST_START("fuzz ECDSA sig"); buf = load_file("ecdsa_1"); diff --git a/regress/unittests/sshkey/test_sshkey.c b/regress/unittests/sshkey/test_sshkey.c index 53bdc0ca62d8..832ef9b202cc 100644 --- a/regress/unittests/sshkey/test_sshkey.c +++ b/regress/unittests/sshkey/test_sshkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: test_sshkey.c,v 1.28 2025/04/15 05:31:24 djm Exp $ */ +/* $OpenBSD: test_sshkey.c,v 1.29 2025/05/06 06:05:48 djm Exp $ */ /* * Regress test for sshkey.h key management API * @@ -18,7 +18,6 @@ #ifdef WITH_OPENSSL #include #include -#include #if defined(OPENSSL_HAS_ECC) && defined(OPENSSL_HAS_NISTP256) # include #endif @@ -271,14 +270,6 @@ sshkey_tests(void) sshkey_free(k1); TEST_DONE(); -#ifdef WITH_DSA - TEST_START("new/free KEY_DSA"); - k1 = sshkey_new(KEY_DSA); - ASSERT_PTR_NE(k1, NULL); - ASSERT_PTR_NE(k1->dsa, NULL); - sshkey_free(k1); - TEST_DONE(); -#endif #ifdef OPENSSL_HAS_ECC TEST_START("new/free KEY_ECDSA"); @@ -310,14 +301,6 @@ sshkey_tests(void) ASSERT_PTR_EQ(k1, NULL); TEST_DONE(); -#ifdef WITH_DSA - TEST_START("generate KEY_DSA wrong bits"); - ASSERT_INT_EQ(sshkey_generate(KEY_DSA, 2048, &k1), - SSH_ERR_KEY_LENGTH); - ASSERT_PTR_EQ(k1, NULL); - sshkey_free(k1); - TEST_DONE(); -#endif #ifdef OPENSSL_HAS_ECC TEST_START("generate KEY_ECDSA wrong bits"); @@ -340,15 +323,6 @@ sshkey_tests(void) ASSERT_INT_EQ(BN_num_bits(rsa_n(kr)), 1024); TEST_DONE(); -#ifdef WITH_DSA - TEST_START("generate KEY_DSA"); - ASSERT_INT_EQ(sshkey_generate(KEY_DSA, 1024, &kd), 0); - ASSERT_PTR_NE(kd, NULL); - ASSERT_PTR_NE(kd->dsa, NULL); - ASSERT_PTR_NE(dsa_g(kd), NULL); - ASSERT_PTR_NE(dsa_priv_key(kd), NULL); - TEST_DONE(); -#endif #ifdef OPENSSL_HAS_ECC TEST_START("generate KEY_ECDSA"); @@ -388,22 +362,6 @@ sshkey_tests(void) sshkey_free(k1); TEST_DONE(); -#ifdef WITH_DSA - TEST_START("demote KEY_DSA"); - ASSERT_INT_EQ(sshkey_from_private(kd, &k1), 0); - ASSERT_PTR_NE(k1, NULL); - ASSERT_PTR_NE(kd, k1); - ASSERT_INT_EQ(k1->type, KEY_DSA); - ASSERT_PTR_NE(k1->dsa, NULL); - ASSERT_PTR_NE(dsa_g(k1), NULL); - ASSERT_PTR_EQ(dsa_priv_key(k1), NULL); - TEST_DONE(); - - TEST_START("equal KEY_DSA/demoted KEY_DSA"); - ASSERT_INT_EQ(sshkey_equal(kd, k1), 1); - sshkey_free(k1); - TEST_DONE(); -#endif #ifdef OPENSSL_HAS_ECC TEST_START("demote KEY_ECDSA"); @@ -551,16 +509,6 @@ sshkey_tests(void) sshkey_free(k2); TEST_DONE(); -#ifdef WITH_DSA - TEST_START("sign and verify DSA"); - k1 = get_private("dsa_1"); - ASSERT_INT_EQ(sshkey_load_public(test_data_file("dsa_2.pub"), &k2, - NULL), 0); - signature_tests(k1, k2, NULL); - sshkey_free(k1); - sshkey_free(k2); - TEST_DONE(); -#endif #ifdef OPENSSL_HAS_ECC TEST_START("sign and verify ECDSA"); @@ -623,15 +571,6 @@ sshkey_benchmarks(void) TEST_DONE(); BENCH_FINISH("keys"); -#ifdef WITH_DSA - BENCH_START("generate DSA-1024"); - TEST_START("generate KEY_DSA"); - ASSERT_INT_EQ(sshkey_generate(KEY_DSA, 1024, &k), 0); - ASSERT_PTR_NE(k, NULL); - sshkey_free(k); - TEST_DONE(); - BENCH_FINISH("keys"); -#endif BENCH_START("generate ECDSA-256"); TEST_START("generate KEY_ECDSA"); @@ -674,9 +613,6 @@ sshkey_benchmarks(void) signature_benchmark("RSA-2048/SHA1", KEY_RSA, 2048, "ssh-rsa", 0); signature_benchmark("RSA-2048/SHA256", KEY_RSA, 2048, "rsa-sha2-256", 0); signature_benchmark("RSA-2048/SHA512", KEY_RSA, 2048, "rsa-sha2-512", 0); -#ifdef WITH_DSA - signature_benchmark("DSA-1024", KEY_DSA, 1024, NULL, 0); -#endif signature_benchmark("ECDSA-256", KEY_ECDSA, 256, NULL, 0); signature_benchmark("ECDSA-384", KEY_ECDSA, 384, NULL, 0); signature_benchmark("ECDSA-521", KEY_ECDSA, 521, NULL, 0); @@ -689,9 +625,6 @@ sshkey_benchmarks(void) signature_benchmark("RSA-2048/SHA1", KEY_RSA, 2048, "ssh-rsa", 1); signature_benchmark("RSA-2048/SHA256", KEY_RSA, 2048, "rsa-sha2-256", 1); signature_benchmark("RSA-2048/SHA512", KEY_RSA, 2048, "rsa-sha2-512", 1); -#ifdef WITH_DSA - signature_benchmark("DSA-1024", KEY_DSA, 1024, NULL, 1); -#endif signature_benchmark("ECDSA-256", KEY_ECDSA, 256, NULL, 1); signature_benchmark("ECDSA-384", KEY_ECDSA, 384, NULL, 1); signature_benchmark("ECDSA-521", KEY_ECDSA, 521, NULL, 1); diff --git a/regress/unittests/sshkey/testdata/dsa_1 b/regress/unittests/sshkey/testdata/dsa_1 deleted file mode 100644 index d3f24824f8d5..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1 +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBvAIBAAKBgQD6kutNFRsHTwEAv6d39Lhsqy1apdHBZ9c2HfyRr7WmypyGIy2m -Ka43vzXI8CNwmRSYs+A6d0vJC7Pl+f9QzJ/04NWOA+MiwfurwrR3CRe61QRYb8Py -mcHOxueHs95IcjrbIPNn86cjnPP5qvv/guUzCjuww4zBdJOXpligrGt2XwIVAKMD -/50qQy7j8JaMk+1+Xtg1pK01AoGBAO7l9QVVbSSoy5lq6cOtvpf8UlwOa6+zBwbl -o4gmFd1RwX1yWkA8kQ7RrhCSg8Hc6mIGnKRgKRli/3LgbSfZ0obFJehkRtEWtN4P -h8fVUeS74iQbIwFQeKlYHIlNTRoGtAbdi3nHdV+BBkEQc1V3rjqYqhjOoz/yNsgz -LND26HrdAoGBAOdXpyfmobEBaOqZAuvgj1P0uhjG2P31Ufurv22FWPBU3A9qrkxb -OXwE0LwvjCvrsQV/lrYhJz/tiys40VeahulWZE5SAHMXGIf95LiLSgaXMjko7joo -t+LK84ltLymwZ4QMnYjnZSSclf1UuyQMcUtb34+I0u9Ycnyhp2mSFsQtAhRYIbQ5 -KfXsZuBPuWe5FJz3ldaEgw== ------END DSA PRIVATE KEY----- diff --git a/regress/unittests/sshkey/testdata/dsa_1-cert.fp b/regress/unittests/sshkey/testdata/dsa_1-cert.fp deleted file mode 100644 index 75ff0e9cd9f7..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1-cert.fp +++ /dev/null @@ -1 +0,0 @@ -SHA256:kOLgXSoAT8O5T6r36n5NJUYigbux1d7gdH/rmWiJm6s diff --git a/regress/unittests/sshkey/testdata/dsa_1-cert.pub b/regress/unittests/sshkey/testdata/dsa_1-cert.pub deleted file mode 100644 index e768db1e7bad..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1-cert.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgdTlbNU9Hn9Qng3FHxwH971bxCIoq1ern/QWFFDWXgmYAAACBAPqS600VGwdPAQC/p3f0uGyrLVql0cFn1zYd/JGvtabKnIYjLaYprje/NcjwI3CZFJiz4Dp3S8kLs+X5/1DMn/Tg1Y4D4yLB+6vCtHcJF7rVBFhvw/KZwc7G54ez3khyOtsg82fzpyOc8/mq+/+C5TMKO7DDjMF0k5emWKCsa3ZfAAAAFQCjA/+dKkMu4/CWjJPtfl7YNaStNQAAAIEA7uX1BVVtJKjLmWrpw62+l/xSXA5rr7MHBuWjiCYV3VHBfXJaQDyRDtGuEJKDwdzqYgacpGApGWL/cuBtJ9nShsUl6GRG0Ra03g+Hx9VR5LviJBsjAVB4qVgciU1NGga0Bt2Lecd1X4EGQRBzVXeuOpiqGM6jP/I2yDMs0Pboet0AAACBAOdXpyfmobEBaOqZAuvgj1P0uhjG2P31Ufurv22FWPBU3A9qrkxbOXwE0LwvjCvrsQV/lrYhJz/tiys40VeahulWZE5SAHMXGIf95LiLSgaXMjko7joot+LK84ltLymwZ4QMnYjnZSSclf1UuyQMcUtb34+I0u9Ycnyhp2mSFsQtAAAAAAAAAAYAAAACAAAABmp1bGl1cwAAABIAAAAFaG9zdDEAAAAFaG9zdDIAAAAANowB8AAAAABNHmBwAAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACBThupGO0X+FLQhbz8CoKPwc7V3JNsQuGtlsgN+F7SMGQAAAFMAAAALc3NoLWVkMjU1MTkAAABAh/z1LIdNL1b66tQ8t9DY9BTB3BQKpTKmc7ezyFKLwl96yaIniZwD9Ticdbe/8i/Li3uCFE3EAt8NAIv9zff8Bg== DSA test key #1 diff --git a/regress/unittests/sshkey/testdata/dsa_1.fp b/regress/unittests/sshkey/testdata/dsa_1.fp deleted file mode 100644 index 75ff0e9cd9f7..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1.fp +++ /dev/null @@ -1 +0,0 @@ -SHA256:kOLgXSoAT8O5T6r36n5NJUYigbux1d7gdH/rmWiJm6s diff --git a/regress/unittests/sshkey/testdata/dsa_1.fp.bb b/regress/unittests/sshkey/testdata/dsa_1.fp.bb deleted file mode 100644 index ba37776ee30a..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1.fp.bb +++ /dev/null @@ -1 +0,0 @@ -xetag-todiz-mifah-torec-mynyv-cyvit-gopon-pygag-rupic-cenav-bexax diff --git a/regress/unittests/sshkey/testdata/dsa_1.param.g b/regress/unittests/sshkey/testdata/dsa_1.param.g deleted file mode 100644 index e51c3f9fd1b4..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1.param.g +++ /dev/null @@ -1 +0,0 @@ -00eee5f505556d24a8cb996ae9c3adbe97fc525c0e6bafb30706e5a3882615dd51c17d725a403c910ed1ae109283c1dcea62069ca460291962ff72e06d27d9d286c525e86446d116b4de0f87c7d551e4bbe2241b23015078a9581c894d4d1a06b406dd8b79c7755f81064110735577ae3a98aa18cea33ff236c8332cd0f6e87add diff --git a/regress/unittests/sshkey/testdata/dsa_1.param.priv b/regress/unittests/sshkey/testdata/dsa_1.param.priv deleted file mode 100644 index 4f743314c76e..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1.param.priv +++ /dev/null @@ -1 +0,0 @@ -5821b43929f5ec66e04fb967b9149cf795d68483 diff --git a/regress/unittests/sshkey/testdata/dsa_1.param.pub b/regress/unittests/sshkey/testdata/dsa_1.param.pub deleted file mode 100644 index ba0313beec48..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1.param.pub +++ /dev/null @@ -1 +0,0 @@ -00e757a727e6a1b10168ea9902ebe08f53f4ba18c6d8fdf551fbabbf6d8558f054dc0f6aae4c5b397c04d0bc2f8c2bebb1057f96b621273fed8b2b38d1579a86e956644e520073171887fde4b88b4a0697323928ee3a28b7e2caf3896d2f29b067840c9d88e765249c95fd54bb240c714b5bdf8f88d2ef58727ca1a7699216c42d diff --git a/regress/unittests/sshkey/testdata/dsa_1.pub b/regress/unittests/sshkey/testdata/dsa_1.pub deleted file mode 100644 index 41cae2f69f52..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBAPqS600VGwdPAQC/p3f0uGyrLVql0cFn1zYd/JGvtabKnIYjLaYprje/NcjwI3CZFJiz4Dp3S8kLs+X5/1DMn/Tg1Y4D4yLB+6vCtHcJF7rVBFhvw/KZwc7G54ez3khyOtsg82fzpyOc8/mq+/+C5TMKO7DDjMF0k5emWKCsa3ZfAAAAFQCjA/+dKkMu4/CWjJPtfl7YNaStNQAAAIEA7uX1BVVtJKjLmWrpw62+l/xSXA5rr7MHBuWjiCYV3VHBfXJaQDyRDtGuEJKDwdzqYgacpGApGWL/cuBtJ9nShsUl6GRG0Ra03g+Hx9VR5LviJBsjAVB4qVgciU1NGga0Bt2Lecd1X4EGQRBzVXeuOpiqGM6jP/I2yDMs0Pboet0AAACBAOdXpyfmobEBaOqZAuvgj1P0uhjG2P31Ufurv22FWPBU3A9qrkxbOXwE0LwvjCvrsQV/lrYhJz/tiys40VeahulWZE5SAHMXGIf95LiLSgaXMjko7joot+LK84ltLymwZ4QMnYjnZSSclf1UuyQMcUtb34+I0u9Ycnyhp2mSFsQt DSA test key #1 diff --git a/regress/unittests/sshkey/testdata/dsa_1_pw b/regress/unittests/sshkey/testdata/dsa_1_pw deleted file mode 100644 index 24c73039fe1a..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_1_pw +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-128-CBC,BC8386C373B22EB7F00ADC821D5D8BE9 - -+HDV2DQ09sxrIAeXTz9r3YFuPRa2hk1+NGcr3ETkXbC6KiZ14wpTnGTloKwaQjIW -eXTa9mpCOWAoohgvsVb+hOuOlP7AfeHu1IXV4EAS+GDpkiV5UxlCXXwqlD75Buu4 -wwDd/p4SWzILH3WGjDk5JIXoxWNY13LHwC7Q6gtGJx4AicUG7YBRTXMIBDa/Kh77 -6o2rFETKmp4VHBvHbakmiETfptdM8bbWxKWeY2vakThyESgeofsLoTOQCIwlEfJC -s2D/KYL65C8VbHYgIoSLTQnooO45DDyxIuhCqP+H23mhv9vB1Od3nc2atgHj/XFs -dcOPFkF/msDRYqxY3V0AS6+jpKwFodZ7g/hyGcyPxOkzlJVuKoKuH6P5PyQ69Gx0 -iqri0xEPyABr7kGlXNrjjctojX+B4WwSnjg/2euXXWFXCRalIdA7ErATTiQbGOx7 -Vd6Gn8PZbSy1MkqEDrZRip0pfAFJYI/8GXPC75BpnRsrVlfhtrngbW+kBP35LzaN -l2K+RQ3gSB3iFoqNb1Kuu6T5MZlyVl5H2dVlJSeb1euQ2OycXdDoFTyJ4AiyWS7w -Vlh8zeJnso5QRDjMwx99pZilbbuFGSLsahiGEveFc6o= ------END DSA PRIVATE KEY----- diff --git a/regress/unittests/sshkey/testdata/dsa_2 b/regress/unittests/sshkey/testdata/dsa_2 deleted file mode 100644 index 3cc9631afa0f..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_2 +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBvQIBAAKBgQCbyPXNdHeLsjpobPVCMkfagBkt15Zsltqf/PGNP1y1cuz7rsTX -ZekQwUkSTNm5coqXe+ZOw2O4tjobJDd60I1/VPgaB0NYlQR9Hn87M284WD4f6VY+ -aunHmP134a8ybG5G4NqVNF3ihvxAR2pVITqb7kE46r2uYZNcNlHI8voRCwIVAMcP -bwqFNsQbH5pJyZW30wj4KVZ3AoGBAIK98BVeKQVf8qDFqx9ovMuNgVSxpd+N0Yta -5ZEy1OI2ziu5RhjueIM2K7Gq2Mnp38ob1AM53BUxqlcBJaHEDa6rj6yvuMgW9oCJ -dImBM8sIFxfBbXNbpJiMaDwa6WyT84OkpDE6uuAepTMnWOUWkUVkAiyokHDUGXkG -GyoQblbXAoGBAIsf7TaZ804sUWwRV0wI8DYx+hxD5QdrfYPYMtL2fHn3lICimGt0 -FTtUZ25jKg0E0DMBPdET6ZEHB3ZZkR8hFoUzZhdnyJMu3UjVtgaV88Ue3PrXxchk -0W2jHPaAgQU3JIWzo8HFIFqvC/HEL+EyW3rBTY2uXM3XGI+YcWSA4ZrZAhUAsY2f -bDFNzgZ4DaZ9wLRzTgOswPU= ------END DSA PRIVATE KEY----- diff --git a/regress/unittests/sshkey/testdata/dsa_2.fp b/regress/unittests/sshkey/testdata/dsa_2.fp deleted file mode 100644 index 51fbeb4d8ce1..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_2.fp +++ /dev/null @@ -1 +0,0 @@ -SHA256:ecwhWcXgpdBxZ2e+OjpRRY7dqXHHCD62BGtoVQQBwCk diff --git a/regress/unittests/sshkey/testdata/dsa_2.fp.bb b/regress/unittests/sshkey/testdata/dsa_2.fp.bb deleted file mode 100644 index 4d908ee30977..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_2.fp.bb +++ /dev/null @@ -1 +0,0 @@ -xeser-megad-pocan-rozit-belup-tapoh-fapif-kyvit-vonav-cehab-naxax diff --git a/regress/unittests/sshkey/testdata/dsa_2.pub b/regress/unittests/sshkey/testdata/dsa_2.pub deleted file mode 100644 index 77bb555d595f..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_2.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBAJvI9c10d4uyOmhs9UIyR9qAGS3XlmyW2p/88Y0/XLVy7PuuxNdl6RDBSRJM2blyipd75k7DY7i2OhskN3rQjX9U+BoHQ1iVBH0efzszbzhYPh/pVj5q6ceY/XfhrzJsbkbg2pU0XeKG/EBHalUhOpvuQTjqva5hk1w2Ucjy+hELAAAAFQDHD28KhTbEGx+aScmVt9MI+ClWdwAAAIEAgr3wFV4pBV/yoMWrH2i8y42BVLGl343Ri1rlkTLU4jbOK7lGGO54gzYrsarYyenfyhvUAzncFTGqVwElocQNrquPrK+4yBb2gIl0iYEzywgXF8Ftc1ukmIxoPBrpbJPzg6SkMTq64B6lMydY5RaRRWQCLKiQcNQZeQYbKhBuVtcAAACBAIsf7TaZ804sUWwRV0wI8DYx+hxD5QdrfYPYMtL2fHn3lICimGt0FTtUZ25jKg0E0DMBPdET6ZEHB3ZZkR8hFoUzZhdnyJMu3UjVtgaV88Ue3PrXxchk0W2jHPaAgQU3JIWzo8HFIFqvC/HEL+EyW3rBTY2uXM3XGI+YcWSA4ZrZ DSA test key #2 diff --git a/regress/unittests/sshkey/testdata/dsa_n b/regress/unittests/sshkey/testdata/dsa_n deleted file mode 100644 index 657624e0e72f..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_n +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABswAAAAdzc2gtZH -NzAAAAgQD6kutNFRsHTwEAv6d39Lhsqy1apdHBZ9c2HfyRr7WmypyGIy2mKa43vzXI8CNw -mRSYs+A6d0vJC7Pl+f9QzJ/04NWOA+MiwfurwrR3CRe61QRYb8PymcHOxueHs95IcjrbIP -Nn86cjnPP5qvv/guUzCjuww4zBdJOXpligrGt2XwAAABUAowP/nSpDLuPwloyT7X5e2DWk -rTUAAACBAO7l9QVVbSSoy5lq6cOtvpf8UlwOa6+zBwblo4gmFd1RwX1yWkA8kQ7RrhCSg8 -Hc6mIGnKRgKRli/3LgbSfZ0obFJehkRtEWtN4Ph8fVUeS74iQbIwFQeKlYHIlNTRoGtAbd -i3nHdV+BBkEQc1V3rjqYqhjOoz/yNsgzLND26HrdAAAAgQDnV6cn5qGxAWjqmQLr4I9T9L -oYxtj99VH7q79thVjwVNwPaq5MWzl8BNC8L4wr67EFf5a2ISc/7YsrONFXmobpVmROUgBz -FxiH/eS4i0oGlzI5KO46KLfiyvOJbS8psGeEDJ2I52UknJX9VLskDHFLW9+PiNLvWHJ8oa -dpkhbELQAAAdhWTOFbVkzhWwAAAAdzc2gtZHNzAAAAgQD6kutNFRsHTwEAv6d39Lhsqy1a -pdHBZ9c2HfyRr7WmypyGIy2mKa43vzXI8CNwmRSYs+A6d0vJC7Pl+f9QzJ/04NWOA+Miwf -urwrR3CRe61QRYb8PymcHOxueHs95IcjrbIPNn86cjnPP5qvv/guUzCjuww4zBdJOXplig -rGt2XwAAABUAowP/nSpDLuPwloyT7X5e2DWkrTUAAACBAO7l9QVVbSSoy5lq6cOtvpf8Ul -wOa6+zBwblo4gmFd1RwX1yWkA8kQ7RrhCSg8Hc6mIGnKRgKRli/3LgbSfZ0obFJehkRtEW -tN4Ph8fVUeS74iQbIwFQeKlYHIlNTRoGtAbdi3nHdV+BBkEQc1V3rjqYqhjOoz/yNsgzLN -D26HrdAAAAgQDnV6cn5qGxAWjqmQLr4I9T9LoYxtj99VH7q79thVjwVNwPaq5MWzl8BNC8 -L4wr67EFf5a2ISc/7YsrONFXmobpVmROUgBzFxiH/eS4i0oGlzI5KO46KLfiyvOJbS8psG -eEDJ2I52UknJX9VLskDHFLW9+PiNLvWHJ8oadpkhbELQAAABRYIbQ5KfXsZuBPuWe5FJz3 -ldaEgwAAAAAB ------END OPENSSH PRIVATE KEY----- diff --git a/regress/unittests/sshkey/testdata/dsa_n_pw b/regress/unittests/sshkey/testdata/dsa_n_pw deleted file mode 100644 index 24ac299a482d..000000000000 --- a/regress/unittests/sshkey/testdata/dsa_n_pw +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABCVs+LsMJ -wnB5zM9U9pTXrGAAAAEAAAAAEAAAGzAAAAB3NzaC1kc3MAAACBAPqS600VGwdPAQC/p3f0 -uGyrLVql0cFn1zYd/JGvtabKnIYjLaYprje/NcjwI3CZFJiz4Dp3S8kLs+X5/1DMn/Tg1Y -4D4yLB+6vCtHcJF7rVBFhvw/KZwc7G54ez3khyOtsg82fzpyOc8/mq+/+C5TMKO7DDjMF0 -k5emWKCsa3ZfAAAAFQCjA/+dKkMu4/CWjJPtfl7YNaStNQAAAIEA7uX1BVVtJKjLmWrpw6 -2+l/xSXA5rr7MHBuWjiCYV3VHBfXJaQDyRDtGuEJKDwdzqYgacpGApGWL/cuBtJ9nShsUl -6GRG0Ra03g+Hx9VR5LviJBsjAVB4qVgciU1NGga0Bt2Lecd1X4EGQRBzVXeuOpiqGM6jP/ -I2yDMs0Pboet0AAACBAOdXpyfmobEBaOqZAuvgj1P0uhjG2P31Ufurv22FWPBU3A9qrkxb -OXwE0LwvjCvrsQV/lrYhJz/tiys40VeahulWZE5SAHMXGIf95LiLSgaXMjko7joot+LK84 -ltLymwZ4QMnYjnZSSclf1UuyQMcUtb34+I0u9Ycnyhp2mSFsQtAAAB4HiOcRW4w+sIqBL0 -TPVbf0glN1hUi0rcE63Pqxmvxb8LkldC4IxAUagPrjhNAEW2AY42+CvPrtGB1z7gDADAIW -xZX6wKwIcXP0Qh+xHE12F4u6mwfasssnAp4t1Ki8uCjMjnimgb3KdWpp0kiUV0oR062TXV -PAdfrWjaq4fw0KOqbHIAG/v36AqzuqjSTfDbqvLZM3y0gp2Q1RxaQVJA5ZIKKyqRyFX7sr -BaEIyCgeE3hM0EB7BycY1oIcS/eNxrACBWVJCENl5N7LtEYXNX7TANFniztfXzwaqGTT6A -fCfbW4gz1UKldLUBzbIrPwMWlirAstbHvOf/2Iay2pNAs/SHhI0aF2jsGfvv5/D6N+r9dG -B2SgDKBg7pywMH1DTvg6YT3P4GjCx0GUHqRCFLvD1rDdk4KSjvaRMpVq1PJ0/Wv6UGtsMS -TR0PaEHDRNZqAX4YxqujnWrGKuRJhuz0eUvp7fZvbWHtiAMKV7368kkeUmkOHanb+TS+zs -KINX8ev8zJZ6WVr8Vl+IQavpv0i2bXwS6QqbEuifpv/+uBb7pqRiU4u8en0eMdX1bZoTPM -R6xHCnGD/Jpb3zS91Ya57T6CiXZ12KCaL6nWGnCkZVpzkfJ2HjFklWSWBQ6uyaosDQ== ------END OPENSSH PRIVATE KEY----- diff --git a/regress/unittests/sshsig/Makefile b/regress/unittests/sshsig/Makefile index bc3c6c739d48..f8b6560eba18 100644 --- a/regress/unittests/sshsig/Makefile +++ b/regress/unittests/sshsig/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.3 2023/01/15 23:35:10 djm Exp $ +# $OpenBSD: Makefile,v 1.4 2025/05/06 06:05:48 djm Exp $ PROG=test_sshsig SRCS=tests.c @@ -6,7 +6,7 @@ SRCS=tests.c # From usr.bin/ssh SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c -SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c +SRCS+=ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c SRCS+=addr.c addrmatch.c bitmap.c sshsig.c SRCS+=ed25519.c hash.c diff --git a/regress/unittests/sshsig/mktestdata.sh b/regress/unittests/sshsig/mktestdata.sh index d2300f9c6ee1..b7c60cc27767 100755 --- a/regress/unittests/sshsig/mktestdata.sh +++ b/regress/unittests/sshsig/mktestdata.sh @@ -1,5 +1,5 @@ #!/bin/sh -# $OpenBSD: mktestdata.sh,v 1.1 2020/06/19 04:32:09 djm Exp $ +# $OpenBSD: mktestdata.sh,v 1.2 2025/05/06 06:05:48 djm Exp $ NAMESPACE=unittest @@ -17,14 +17,13 @@ else fi rm -f signed-data namespace -rm -f rsa dsa ecdsa ed25519 ecdsa_sk ed25519_sk -rm -f rsa.sig dsa.sig ecdsa.sig ed25519.sig ecdsa_sk.sig ed25519_sk.sig +rm -f rsa ecdsa ed25519 ecdsa_sk ed25519_sk +rm -f rsa.sig ecdsa.sig ed25519.sig ecdsa_sk.sig ed25519_sk.sig printf "This is a test, this is only a test" > signed-data printf "$NAMESPACE" > namespace ssh-keygen -t rsa -C "RSA test" -N "" -f rsa -m PEM -ssh-keygen -t dsa -C "DSA test" -N "" -f dsa -m PEM ssh-keygen -t ecdsa -C "ECDSA test" -N "" -f ecdsa -m PEM ssh-keygen -t ed25519 -C "ED25519 test key" -N "" -f ed25519 ssh-keygen -w "$SK_DUMMY" -t ecdsa-sk -C "ECDSA-SK test key" \ @@ -33,7 +32,6 @@ ssh-keygen -w "$SK_DUMMY" -t ed25519-sk -C "ED25519-SK test key" \ -N "" -f ed25519_sk ssh-keygen -Y sign -f rsa -n $NAMESPACE - < signed-data > rsa.sig -ssh-keygen -Y sign -f dsa -n $NAMESPACE - < signed-data > dsa.sig ssh-keygen -Y sign -f ecdsa -n $NAMESPACE - < signed-data > ecdsa.sig ssh-keygen -Y sign -f ed25519 -n $NAMESPACE - < signed-data > ed25519.sig ssh-keygen -w "$SK_DUMMY" \ diff --git a/regress/unittests/sshsig/testdata/dsa b/regress/unittests/sshsig/testdata/dsa deleted file mode 100644 index 7c0063efcdf5..000000000000 --- a/regress/unittests/sshsig/testdata/dsa +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBuwIBAAKBgQCXpndQdz2mQVnk+lYOF3nxDT+h6SiJmUvBFhnFWBv8tG4pTOkb -EwGufLEzGpzjTj+3bjVau7LFt37AFrqs4Num272BWNsYNIjOlGPgq7Xjv32FN00x -JYh1DoRs1cGGnvohlsWEamGGhTHD1a9ipctPEBV+NrxtZMrl+pO/ZZg8vQIVAKJB -P3iNYSpSuW74+q4WxLCuK8O3AoGAQldE+BIuxlvoG1IFiWesx0CU+H2KO0SEZc9A -SX/qjOabh0Fb78ofTlEf9gWHFfat8SvSJQIOPMVlb76Lio8AAMT8Eaa/qQKKYmQL -dNq4MLhhjxx5KLGt6J2JyFPExCv+qnHYHD59ngtLwKyqGjpSC8LPLktdXn8W/Aad -Ly1K7+MCgYBsMHBczhSeUh8w7i20CVg4OlNTmfJRVU2tO6OpMxZ/quitRm3hLKSN -u4xRkvHJwi4LhQtv1SXvLI5gs5P3gCG8tsIAiyCqLinHha63iBdJpqhnV/x/j7dB -yJr3xJbnmLdWLkkCtNk1Ir1/CuEz+ufAyLGdKWksEAu1UUlb501BkwIVAILIa3Rg -0h7J9lQpHJphvF3K0M1T ------END DSA PRIVATE KEY----- diff --git a/regress/unittests/sshsig/testdata/dsa.pub b/regress/unittests/sshsig/testdata/dsa.pub deleted file mode 100644 index e77aa7ef41a0..000000000000 --- a/regress/unittests/sshsig/testdata/dsa.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-dss AAAAB3NzaC1kc3MAAACBAJemd1B3PaZBWeT6Vg4XefENP6HpKImZS8EWGcVYG/y0bilM6RsTAa58sTManONOP7duNVq7ssW3fsAWuqzg26bbvYFY2xg0iM6UY+CrteO/fYU3TTEliHUOhGzVwYae+iGWxYRqYYaFMcPVr2Kly08QFX42vG1kyuX6k79lmDy9AAAAFQCiQT94jWEqUrlu+PquFsSwrivDtwAAAIBCV0T4Ei7GW+gbUgWJZ6zHQJT4fYo7RIRlz0BJf+qM5puHQVvvyh9OUR/2BYcV9q3xK9IlAg48xWVvvouKjwAAxPwRpr+pAopiZAt02rgwuGGPHHkosa3onYnIU8TEK/6qcdgcPn2eC0vArKoaOlILws8uS11efxb8Bp0vLUrv4wAAAIBsMHBczhSeUh8w7i20CVg4OlNTmfJRVU2tO6OpMxZ/quitRm3hLKSNu4xRkvHJwi4LhQtv1SXvLI5gs5P3gCG8tsIAiyCqLinHha63iBdJpqhnV/x/j7dByJr3xJbnmLdWLkkCtNk1Ir1/CuEz+ufAyLGdKWksEAu1UUlb501Bkw== DSA test diff --git a/regress/unittests/sshsig/testdata/dsa.sig b/regress/unittests/sshsig/testdata/dsa.sig deleted file mode 100644 index 0b14ad6b8a7b..000000000000 --- a/regress/unittests/sshsig/testdata/dsa.sig +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN SSH SIGNATURE----- -U1NIU0lHAAAAAQAAAbEAAAAHc3NoLWRzcwAAAIEAl6Z3UHc9pkFZ5PpWDhd58Q0/oekoiZ -lLwRYZxVgb/LRuKUzpGxMBrnyxMxqc404/t241Wruyxbd+wBa6rODbptu9gVjbGDSIzpRj -4Ku14799hTdNMSWIdQ6EbNXBhp76IZbFhGphhoUxw9WvYqXLTxAVfja8bWTK5fqTv2WYPL -0AAAAVAKJBP3iNYSpSuW74+q4WxLCuK8O3AAAAgEJXRPgSLsZb6BtSBYlnrMdAlPh9ijtE -hGXPQEl/6ozmm4dBW+/KH05RH/YFhxX2rfEr0iUCDjzFZW++i4qPAADE/BGmv6kCimJkC3 -TauDC4YY8ceSixreidichTxMQr/qpx2Bw+fZ4LS8Csqho6UgvCzy5LXV5/FvwGnS8tSu/j -AAAAgGwwcFzOFJ5SHzDuLbQJWDg6U1OZ8lFVTa07o6kzFn+q6K1GbeEspI27jFGS8cnCLg -uFC2/VJe8sjmCzk/eAIby2wgCLIKouKceFrreIF0mmqGdX/H+Pt0HImvfElueYt1YuSQK0 -2TUivX8K4TP658DIsZ0paSwQC7VRSVvnTUGTAAAACHVuaXR0ZXN0AAAAAAAAAAZzaGE1MT -IAAAA3AAAAB3NzaC1kc3MAAAAodi5lr0pqBpO76OY4N1CtfR85BCgZ95qfVjP/e9lToj0q -lwjSJJXUjw== ------END SSH SIGNATURE----- diff --git a/regress/unittests/sshsig/tests.c b/regress/unittests/sshsig/tests.c index 7fcf9488d270..ef1a46edcbb2 100644 --- a/regress/unittests/sshsig/tests.c +++ b/regress/unittests/sshsig/tests.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tests.c,v 1.5 2025/04/15 04:00:42 djm Exp $ */ +/* $OpenBSD: tests.c,v 1.6 2025/05/06 06:05:48 djm Exp $ */ /* * Regress test for sshbuf.h buffer API * @@ -103,11 +103,6 @@ tests(void) check_sig("rsa.pub", "rsa.sig", msg, namespace); TEST_DONE(); -#ifdef WITH_DSA - TEST_START("check DSA signature"); - check_sig("dsa.pub", "dsa.sig", msg, namespace); - TEST_DONE(); -#endif #ifdef OPENSSL_HAS_ECC TEST_START("check ECDSA signature"); From d41503a5762680e43a8d31e4ec2b9b684028f378 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 11:51:41 -0800 Subject: [PATCH 32/68] add GitHub Copilot files --- .github/agents/merge-upstream.agent.md | 399 ++++++++++++++ .github/instructions/build.instructions.md | 300 +++++++++++ .../getting-started.instructions.md | 28 + .../merge/merge-details.instructions.md | 504 ++++++++++++++++++ .../merge-process-overview.instructions.md | 280 ++++++++++ .../merge/research.instructions.md | 106 ++++ .../repository-overview.instructions.md | 131 +++++ .github/instructions/setup.instructions.md | 86 +++ .github/instructions/testing.instructions.md | 248 +++++++++ .github/tools/Build-OpenSSH.ps1 | 254 +++++++++ .github/tools/Get-CommitGroups.ps1 | 402 ++++++++++++++ .github/tools/Start-OpenSSHBuild.ps1 | 195 +++++++ .github/tools/Test-OpenSSHBuild.ps1 | 262 +++++++++ .github/tools/Test-OpenSSHFunctionality.ps1 | 447 ++++++++++++++++ 14 files changed, 3642 insertions(+) create mode 100644 .github/agents/merge-upstream.agent.md create mode 100644 .github/instructions/build.instructions.md create mode 100644 .github/instructions/getting-started.instructions.md create mode 100644 .github/instructions/merge/merge-details.instructions.md create mode 100644 .github/instructions/merge/merge-process-overview.instructions.md create mode 100644 .github/instructions/merge/research.instructions.md create mode 100644 .github/instructions/repository-overview.instructions.md create mode 100644 .github/instructions/setup.instructions.md create mode 100644 .github/instructions/testing.instructions.md create mode 100644 .github/tools/Build-OpenSSH.ps1 create mode 100644 .github/tools/Get-CommitGroups.ps1 create mode 100644 .github/tools/Start-OpenSSHBuild.ps1 create mode 100644 .github/tools/Test-OpenSSHBuild.ps1 create mode 100644 .github/tools/Test-OpenSSHFunctionality.ps1 diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md new file mode 100644 index 000000000000..a5565b25b883 --- /dev/null +++ b/.github/agents/merge-upstream.agent.md @@ -0,0 +1,399 @@ +--- +name: merge-upstream +description: Assist with merging upstream OpenSSH commits into the PowerShell fork. +tools: + ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'test-server/*', 'agent', 'todo'] +--- +# OpenSSH Upstream Merge Agent + +## Agent Purpose +This agent assists with merging upstream OpenSSH commits into the PowerShell fork (PowerShell/openssh-portable). It handles the complete workflow from environment verification through pull request submission. + +**Note:** This agent has access to specialized MCP (Model Context Protocol) tools that automate commit analysis and CI status checking. When available, use the MCP tools instead of running scripts manually. + +## Agent Capabilities + +### Core Functions +- **Environment verification and setup** +- **Commit group analysis** using Get-CommitGroups MCP tool +- **Incremental cherry-picking** with conflict resolution +- **Windows-specific build system updates** +- **Compilation and testing** +- **Documentation and PR preparation** + +### Key Tools Available + +1. **Get-CommitGroups MCP Tool** - Groups commits by CI presence or success + - **Access**: Available via MCP server (preferred method) + - **MCP Tool Name**: `mcp_pwsh-mcp-server_Get_CommitGroups` + - **Direct Script**: `.github/tools/Get-CommitGroups.ps1` (fallback) + - **Parameters**: + - `GitHubTag` (string, optional): GitHub tag to start from (e.g., "V_10_0_P2") + - `StartCommit` (string, optional): Commit SHA to start from + - `FirstChunkOnly` (boolean, optional): Stop after finding first chunk + - `GroupByCIPresence` (boolean, optional): Group by CI presence instead of CI success + - **Recommended Usage**: Always use `-FirstChunkOnly -GroupByCIPresence` for incremental merging + - **Usage via MCP**: Use the MCP tool function directly - it handles all GitHub API calls + - **Manual Usage**: `Get-Help .\Get-CommitGroups.ps1` for direct script execution + +2. **OpenSSHBuildHelper Module** - Build automation + - Location: `contrib\win32\openssh\OpenSSHBuildHelper.psm1` + - Key cmdlet: `Start-OpenSSHBuild -Configuration Release -NativeHostArch x64` + +3. **Git** - Version control operations + - Cherry-pick: `git cherry-pick start_commit^..end_commit` (inclusive) + - Status: `git status` + - Remotes: `origin`, `upstream-pwsh`, `upstream` + +## Workflow Phases + +### Phase 0: Research and Planning +**Objective:** Understand the merge scope, requirements, and context + +**Steps:** +1. **Read all merge instructions** from `.github/instructions/merge/` folder: + - `merge-process-overview.instructions.md` - Primary merge workflow and process overview + - `research.instructions.md` - Research methodology and analysis requirements + - `merge-details.instructions.md` - Detailed conflict resolution strategies and patterns + +2. **Analyze upstream changes:** + - Review release notes for target version + - Identify breaking changes and new features + - Note security fixes and critical updates + - Assess Windows-specific impact + +3. **Review historical context:** + - Examine previous merge PRs for patterns + - Identify recurring conflict areas + - Note Windows-specific workarounds from past merges + - Document lessons learned from previous merges + +**Success Criteria:** +- All merge instructions read and understood +- Upstream changes analyzed and documented +- Ready to proceed with environment setup + +### Phase 1: Pre-Merge Setup +**Objective:** Verify environment and establish baseline + +**Steps:** +1. Verify Git, PowerShell, Visual Studio are available +2. Confirm repository remotes are configured +3. Identify upstream version/tag to merge +4. Create merge branch: `merge-v-` +5. Perform baseline build on `latestw_all` branch +6. Use Get-CommitGroups with `-FirstChunkOnly -GroupByCIPresence` to get first commit batch + +**Success Criteria:** +- Base branch builds successfully +- First commit group identified (ending with any CI run) +- Merge branch created + +### Phase 2: Incremental Merge +**Objective:** Cherry-pick commits in a single batch ending with a CI run + +**Steps:** +1. **Get first commit batch** using Get-CommitGroups with `-FirstChunkOnly -GroupByCIPresence`: + ```pwsh + # For first batch - start from last merged tag + .github\tools\Get-CommitGroups.ps1 -GitHubTag "V_10_0_P2" -FirstChunkOnly -GroupByCIPresence + + # For subsequent batches - start from last batch's end commit + .github\tools\Get-CommitGroups.ps1 -StartCommit "" -FirstChunkOnly -GroupByCIPresence + + # The tool returns structured data: + # { + # "ChunkNumber": 1, + # "StartCommit": "609fe2c", + # "EndCommit": "6fb728d", + # "StartCommitFull": "609fe2cae2459d721ac11d23cd27b8a94397ef3c", + # "EndCommitFull": "6fb728df50c1afd338cb0223a84ce24579577eff", + # "CommitCount": 12, + # "StartMessage": "upstream: rework the text for -3 to make it clearer", + # "EndMessage": "Run all tests on Cygwin again." + # } + ``` + +2. **Display batch information for verification:** + ```pwsh + Write-Host "Processing Batch $($result.ChunkNumber)" -ForegroundColor Cyan + Write-Host "Commit Range: $($result.StartCommit)..$($result.EndCommit)" -ForegroundColor Gray + Write-Host "Start: [$($result.StartCommit)] $($result.StartMessage)" -ForegroundColor White + Write-Host "End: [$($result.EndCommit)] $($result.EndMessage)" -ForegroundColor Green + Write-Host "Total commits: $($result.CommitCount)" -ForegroundColor Yellow + ``` + +3. **Cherry-pick the batch:** + ```pwsh + git cherry-pick $($result.StartCommitFull)^..$($result.EndCommitFull) + ``` + +4. **Resolve conflicts** following Windows preservation patterns (if conflicts occur) +5. **Stage and continue:** `git add .` then `git cherry-pick --continue` +6. **Repeat steps 4-5** until all commits in batch are applied + +**Conflict Resolution Patterns:** +- **Windows-specific code:** Preserve with `#ifdef WINDOWS` +- **Removed featureand Validation +**Objective:** Build successfully and validate if CI was successful + +**MANDATORY:** Build after each commit batch before proceeding to next batch. + +**Steps:** +1. **Build the merged code:** + ```pwsh + .github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 + ``` + +2. **If build fails, fix compilation errors:** + - Document all compilation errors from build output + - Fix source code issues (add Windows compatibility defines, update function signatures, add platform guards) + - Update Visual Studio project files (add/remove source files, create projects for new binaries) + - Rebuild until successful + - Commit build fixes with detailed description + +3. **Check if batch ended with successful CI:** + - Inspect the chunk's end commit CI status from Get-CommitGroups output + - Look for commits ending with `CIStatus: "success"` in the detailed output + +4. **If CI was successful, run validation tests:** + ```pwsh + .github\tools\Test-OpenSSHFunctionality.ps1 + ``` + - This test installs service, creates test user, validates SSH connectivity + - If tests fail, fix issues and commit fixes + - **Do not proceed** to next batch until tests pass + +5. **If CI was not successful (or no CI), skip validation:** + - Build success is sufficient to proceed + - Validation will be performed at next successful CI checkpoint + +**Common Build Fixes:** +- Missing source files in .vcxproj files +- Removed files still referenced in projects +- Missing function implementations (add to win32compat) + +**Success Criteria:** +- Clean build with no errors +- All expected binaries generated +- If batch had successful CI: validation tests pass +- If batch had no/failed CI: build success is sufficient + +### Phase 4: Summary and Approval +**Objective:** Summarize changes and get approval before proceeding + +**MANDATORY:** Before proceeding to the next commit batch, provide summary and wait for user approval. + +**Steps:** +1. **Generate batch summary:** + ```markdown + ## Batch Completion Summary + + **Batch:** [StartCommit]..[EndCommit] ( commits) + **End Commit CI Status:** + + ### Changes in this Batch: + - + - + - + + ### Conflicts Resolved: + - : + - : + + ### Build Status: + - Build: ✅ Success / ❌ Failed + - Build fixes applied: + + ### Validation Status: + - Validation tests: ✅ Passed / ⏭️ Skipped (no successful CI) / ❌ Failed + - Test fixes applied: + + ### Next Steps: + - Next batch will start from commit: + - Estimated remaining batches: + + **Ready to proceed to next batch?** + ``` + +2. **Wait for user response:** + - User responds "yes" / "continue" / "proceed" → Continue to Phase 5 + - User responds "no" / "stop" / "wait" → Pause and await further instructions + - User provides feedback → Address concerns and re-summarize + +**Success Criteria:** +- Summary provided with all required information +- User approval received to proceed + +### Phase 5: Iteration +**Objective:** Process remaining commit groups until merge is complete + +**Steps:** +1. **Return to Phase 2** with `-StartCommit` set to previous batch's end commit +2. **Repeat Phases 2-4** for each batch: + - Get next batch with Get-CommitGroups + - Cherry-pick commits + - Build (mandatory) + - Validate (if batch ends with successful CI) + - Summarize and get approval +3. **Continue** until all target commits are merged or HEAD is reached +4. **Perform final comprehensive validation:** + ```pwsh + .github\tools\Test-OpenSSHFunctionality.ps1 + ``` + +**Success Criteria:** +- All commit batches processed +- Build remains stable after each batch +- All successful CI checkpoints validated +- Final validation passes +1. Use Get-CommitGroups MCP tool with `-StartCommit` parameter set to previous batch end commit +2. Repeat Phase 2-4 for next batch +3. Continue until all target commits merged +4. Perform final comprehensive test + +**Success Criteria:** +- All commit groups processed +- Build remains stable after each batch +- Tests pass after each batch + +### Phase 6: Documentation and PR +**Objective:** Document changes and submit for review + +**Steps:** +1. Review all merge commits for clarity +2. Document major conflict resolutions +3. Note any Windows-specific changes +4. Push branch: `git push origin merge-v-` +5. Create PR with comprehensive description +6. Add labels and request reviewers + +**PR Description Template:** +```markdown +## Merge OpenSSH + +This PR merges upstream OpenSSH commits from through . + +### Commit Groups +- Batch 1: to ( commits) +- Batch 2: to ( commits) +... + +### Major Changes +- [List significant upstream changes] + +### Windows-Specific Resolutions +- [List Windows compatibility fixes] +- [List build system updates] + +### Testing +- [x] Builds successfully (x64) +- [x] Service starts and runs +- [x] SSH connections work +- [x] Basic operations verified + +### Known Issues +- [List any known limitations or issues] +``` + +**Success Criteria:** +- PR created with complete description +- All CI checks passing +- Reviewers assigned + +## Decision Points + +### When to Use Commit Groups +- **High complexity merge:** >100 commits, significant changes +- **Medium complexity merge:** 50-100 commits, moderate changes +- **Low complexity merge:** <50 commits, minor changes → single merge OK + +### Conflict Resolution Strategy +**Always preserve:** +- Windows-specific functionality in `#ifdef WINDOWS` blocks +- Security fixes from upstream +- Build system integrity + +**Accept upstream for:** +- Removed deprecated features (e.g., DSA) +- Algorithm updates +- API modernization + +**Combine when:** +- Both sides add different functionality +- Windows needs additional compatibility code +- Upstream changes affect Windows-specific code + +## Key Files to Monitor + +### Frequently Modified +- `config.h` / `config.h.vs` - Configuration defines +- `*.vcxproj` - Visual Studio project files +- `contrib/win32/win32compat/*` - Windows compatibility layer + +### Conflict Hotspots +- Authentication code (`auth*.c`) +- Platform-specific code (`platform*.c`) + +## Recovery Procedures + +### Abort Cherry-Pick +```bash +git cherry-pick --abort +git clean -fd +git reset --hard +``` + +### Restart from Checkpoint +```bash +git checkout merge-v- +git log --oneline -5 # Verify last successful commit +# Continue from there +``` + +### Build Failure Recovery +1. Check build log: `contrib\win32\openssh\OpenSSHReleasex64.log` +2. Search for "error C" or "error LNK" +3. Fix errors in order (compilation before linking) +4. Commit fixes separately for clarity + +## Best Practices + +### Commit Organization +- One logical fix per commit +- Clear commit messages explaining Windows-specific changes +- Reference upstream commit SHAs when applicable + +### Testing Between Batches +- Build after each batch +- Quick smoke test (service start, simple connection) +- Full test only after all batches complete + +### Documentation +- Comment complex conflict resolutions in code +- Note Windows-specific workarounds +- Link to upstream issues/PRs when relevant + +## Success Metrics +- ✅ All commits from target range merged +- ✅ Clean build with no warnings +- ✅ All tests passing +- ✅ SSH service functional +- ✅ PR approved and merged +- ✅ No regressions reported in first 48 hours + +## Reference Links + +### Repository Links +- [Upstream OpenSSH](https://github.com/openssh/openssh-portable) +- [PowerShell Fork](https://github.com/PowerShell/openssh-portable) + +### Instruction Documents (Phase 0 Required Reading) +- [Merge Process Overview](../instructions/merge/merge-process-overview.instructions.md) +- [Research Instructions](../instructions/merge/research.instructions.md) +- [Merge Details Instructions](../instructions/merge/merge-details.instructions.md) + +### Additional Instructions +- [Build Instructions](../instructions/build.instructions.md) +- [Setup Instructions](../instructions/setup.instructions.md) +- [Testing Instructions](../instructions/testing.instructions.md) diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md new file mode 100644 index 000000000000..82f267f30cd9 --- /dev/null +++ b/.github/instructions/build.instructions.md @@ -0,0 +1,300 @@ +--- +applyTo: "**/*" +--- + +# Build Instructions for AI Agents + +## Overview +This document provides comprehensive build instructions for OpenSSH-Portable on Windows, specifically tailored for AI agents performing upstream merges. + +## Prerequisites Verification + +### Check Required Tools +```pwsh +# Verify PowerShell version +$PSVersionTable.PSVersion + +# Verify Visual Studio installation +Get-ChildItem "C:\Program Files*\Microsoft Visual Studio\*\*\MSBuild\Current\Bin\msbuild.exe" + +# Verify Windows SDK +Get-ChildItem "C:\Program Files*\Windows Kits\10\bin" -Directory + +# Verify Git +git --version +``` + +## Build Process + +### Using MCP Build Tools (Recommended) + +The repository includes MCP tools that automate the build, verification, and error analysis process. + +#### Option 1: Build & Verify (Recommended) +```pwsh +# Complete build and verification in one step +.\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 + +# Clean build +.\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 -Clean + +# Debug build +.\.github\tools\Build-OpenSSH.ps1 -Configuration Debug -Architecture x64 +``` + +**Output includes:** +- Build success/failure status +- All 14 expected artifacts verification +- Parsed compilation errors with file/line/code/message +- Build warnings summary +- Build log location + +#### Option 2: Build Only +```pwsh +# Incremental build +.\.github\tools\Start-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 + +# Clean build +.\.github\tools\Start-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 -Clean +``` + +#### Option 3: Test Existing Build +```pwsh +# Test artifacts and parse errors from previous build +.\.github\tools\Test-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 +``` + +### Using PowerShell Module Directly (Alternative) + +If you need direct access to the build module functions: + +#### Step 1: Environment Setup +```pwsh +# Navigate to repository root +cd + +# Import build helper module +Import-Module .\contrib\win32\openssh\OpenSSHBuildHelper.psm1 -Force + +# Verify module loaded +Get-Command -Module OpenSSHBuildHelper +``` + +#### Step 2: Initial Build +```pwsh +Start-OpenSSHBuild -Configuration Release -NativeHostArch x64 +``` + +## Compilation Error Resolution + +### Common Error Categories + +#### 1. Missing Preprocessor Definitions +**Symptoms:** +``` +error C2065: 'SOME_DEFINE': undeclared identifier +``` + +**Resolution:** +```pwsh +# Edit config.h.vs file +notepad .\contrib\win32\openssh\config.h.vs + +# Add missing definitions (example) +#define SOME_DEFINE 1 +``` + +#### 2. Missing Windows Equivalents +**Symptoms:** +``` +error C3861: 'fork': identifier not found +error C3861: 'signal': identifier not found +``` + +**Resolution Pattern:** +```c +// In source file, add Windows compatibility +#ifdef WINDOWS + // Use Windows equivalent or win32compat function + HANDLE process = CreateProcess(...); +#else + // Original Unix code + pid_t pid = fork(); +#endif +``` + +#### 3. Build System Inconsistencies +**Symptoms:** +``` +error MSB3073: The command exited with code 1 +fatal error C1083: Cannot open source file: 'newfile.c' +``` + +**AI Agent Resolution Process:** +1. **Check Makefile changes:** + ```bash + git diff upstream-pwsh/latestw_all upstream/ -- Makefile.in + ``` + +2. **Identify new/removed source files:** + ```bash + # Look for patterns like: + # ssh_SOURCES = ssh.c readconf.c clientloop.c sshtty.c \ + # sshconnect.c sshconnect2.c mux.c newfile.c + ``` + +3. **Update Visual Studio projects:** + ```xml + + + ``` + +4. **Update solution if new binaries added:** + ``` + # Check for new programs in Makefile: + # bin_PROGRAMS = ssh sshd ssh-add ssh-keygen ssh-keyscan ssh-copy-id scp sftp sftp-server ssh-pkcs11-helper ssh-sk-helper ssh-agent new-binary + ``` + +### Step-by-Step Error Resolution + +#### AI Agent Workflow: +1. **Attempt initial build using MCP tool** + ```pwsh + .\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 + ``` +2. **Review structured error output** from Build-OpenSSH tool +3. **Categorize error types** (preprocessor, Windows compatibility, build system) +4. **Apply appropriate resolution strategy** (see error categories above) +5. **Rebuild and verify** using Build-OpenSSH tool again +6. **Commit fixes with detailed message** + +#### Manual Error Analysis Commands (if needed): +```pwsh +# Parse errors manually from log file +.\.github\tools\Test-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 + +# Direct MSBuild with detailed logging +& "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" .\contrib\win32\openssh\Win32-OpenSSH.sln /p:Configuration=Release /p:Platform=x64 /v:detailed +``` + +## Project File Management + +### Understanding the Project Structure +``` +contrib\win32\openssh\ +├── Win32-OpenSSH.sln # Main solution file +├── libssh.vcxproj # Core SSH library +├── ssh.vcxproj # SSH client +├── sshd.vcxproj # SSH server listener handling +├── sshd-auth.vcxproj # SSH server authentication handling +├── sshd-session.vcxproj # SSH server session handling +├── ssh-add.vcxproj # Key agent utility +├── ssh-agent.vcxproj # Authentication agent +├── ssh-keygen.vcxproj # Key generation utility +├── ssh-keyscan.vcxproj # Key scanning utility +└── ssh-pkcs11-helper.vcxproj # SSH PKCS11 helper +└── ssh-shell-host.vcxproj # SSH shell host +└── ssh-sk-helper.vcxproj # SSH SK helper +├── scp.vcxproj # Secure copy +├── sftp.vcxproj # Secure file transfer client +└── sftp-server.vcxproj # Secure file transfer server +``` + +### Adding New Projects (AI Agent Process) +1. **Identify new binary in Makefile:** + ```bash + grep "bin_PROGRAMS\|sbin_PROGRAMS" Makefile.in + ``` + +2. **Check Windows applicability:** + ```bash + # Skip Unix-only binaries like ssh-keysign + # Include utilities that work on Windows + ``` + +3. **Create new project file:** + ```pwsh + # Copy existing similar project + Copy-Item ssh.vcxproj ssh-newutil.vcxproj + ``` + +4. **Update project references:** + ```xml + + + ssh-newutil + ssh-newutil + + ``` + +5. **Add to solution:** + ``` + # Edit Win32-OpenSSH.sln to include new project + ``` + +## Validation and Testing + +### Build Verification Using MCP Tools +```pwsh +# Recommended: Use Build-OpenSSH which includes testing +.\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 + +# Or test an existing build +.\.github\tools\Test-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 +``` + +**Expected Output:** +- Success/failure status +- 14 of 14 artifacts found +- Parsed errors and warnings +- Build log location + +**Expected Artifacts (14 executables):** +- ssh.exe, sshd.exe, sshd-auth.exe, sshd-session.exe +- ssh-agent.exe, ssh-add.exe, ssh-keygen.exe, ssh-keyscan.exe +- scp.exe, sftp.exe, sftp-server.exe +- ssh-pkcs11-helper.exe, ssh-shellhost.exe, ssh-sk-helper.exe + +### Manual Verification (Alternative) +```pwsh +# Check that all expected binaries were built +$buildPath = ".\contrib\win32\openssh\x64\Release" +Get-ChildItem $buildPath -Filter "*.exe" | Select-Object Name + +# Expected outputs: +# a binary for each vcxproj file, e.g. +# ssh.vcxproj -> ssh.exe +``` + +### Quick Functionality Test +```pwsh +# Verify version reporting +& ".\contrib\win32\openssh\x64\Release\ssh.exe" -V +``` + +## Troubleshooting Guide + +### Build Helper Module Issues +```pwsh +# Force reload module +Remove-Module OpenSSHBuildHelper -Force -ErrorAction SilentlyContinue +Import-Module .\contrib\win32\openssh\OpenSSHBuildHelper.psm1 -Force +``` + +### Path and Environment Issues +```pwsh +# Verify Visual Studio environment +& "${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" + +# Check MSBuild path +where.exe msbuild +``` + +### Permission Issues +```pwsh +# Ensure running as Administrator if needed +if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) +{ + Write-Warning "Build may require Administrator privileges" +} +``` diff --git a/.github/instructions/getting-started.instructions.md b/.github/instructions/getting-started.instructions.md new file mode 100644 index 000000000000..d4f064517e42 --- /dev/null +++ b/.github/instructions/getting-started.instructions.md @@ -0,0 +1,28 @@ +### 🔧 General Repository Instructions +These instructions apply to any task involving the OpenSSH-Portable repository: + +- **[repository-overview.instructions.md](./repository-overview.instructions.md)** - Repository structure and Windows compatibility layer overview +- **[setup.instructions.md](./setup.instructions.md)** - Repository setup and environment configuration +- **[build.instructions.md](./build.instructions.md)** - Compilation procedures and error resolution +- **[testing.instructions.md](./testing.instructions.md)** - Validation and testing procedures + +### 🔀 Upstream Merge Task Instructions +These instructions are specific to performing upstream merges from openssh/openssh-portable: + +- **[merge/merge-process-overview.instructions.md](./merge/merge-process-overview.instructions.md)** - Primary merge workflow and process overview +- **[merge/merge-details.instructions.md](./merge/merge-details.instructions.md)** - Detailed conflict resolution strategies and patterns +- **[merge/research.instructions.md](./merge/research.instructions.md)** - Intelligence gathering and analysis procedures + +## Quick Start Guide + +### For Human Developers +1. Read [merge/merge-process-overview.instructions.md](./merge/merge-process-overview.instructions.md) for complete merge workflow overview +2. Follow phase-by-phase process +3. Reference general repository instructions ([setup](./setup.instructions.md), [build](./build.instructions.md), [testing](./testing.instructions.md)) as needed + +### For AI Agents +1. **Start here:** [merge/merge-process-overview.instructions.md](./merge/merge-process-overview.instructions.md) +2. **Understand the repository:** Review general instructions for setup, build, and testing procedures +3. **Follow systematic approach:** Check off each merge item before proceeding +4. **Use automation:** Leverage provided scripts in `tools/` directory +5. **Escalate if needed:** Document blockers and seek human guidance when complexity exceeds capabilities diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md new file mode 100644 index 000000000000..b2214858543c --- /dev/null +++ b/.github/instructions/merge/merge-details.instructions.md @@ -0,0 +1,504 @@ +--- +applyTo: "**/*" +--- + +# Merge Instructions for AI Agents + +## Overview +This AI-specific documentation provides comprehensive instructions and algorithmic frameworks that AI agents can use to systematically approach the OpenSSH merge process with minimal human intervention while maintaining high quality and consistency. It combines conflict resolution strategies with automated decision-making processes. + +**Key Approach: Chunked Merging** +Instead of merging entire upstream versions at once, this framework implements a chunked approach that: +- Processes commits in small batches ending with commits that have any CI run (successful or not) +- Groups commits by CI presence rather than CI success for more frequent checkpoints +- Builds after each batch (mandatory) +- Validates functionality only at successful CI checkpoints +- Requires user approval before proceeding to next batch +- Allows for incremental progress and easier rollback +- Reduces complexity of conflict resolution +- Enables better testing and validation at each step + +## Decision Framework for AI Agents + +### Pre-Merge Analysis Algorithm +```pseudocode +FUNCTION analyze_upstream_changes(target_version): + // Step 1: Find last merged commit and determine range + last_upstream_commit = find_last_upstream_commit_in_fork() + commit_range = get_commit_range(last_upstream_commit, target_version) + + // Step 2: Fetch and analyze release notes + release_notes = fetch_release_notes(target_version) + risk_factors = [] + + FOR EACH change IN release_notes: + IF change.contains(["signal", "fork", "pipe", "process", "daemon", "service"]): + risk_factors.append({type: "PROCESS_MANAGEMENT", change: change}) + IF change.contains(["auth", "pam", "kerberos", "gssapi"]): + risk_factors.append({type: "AUTHENTICATION", change: change}) + IF change.contains(["build", "makefile", "configure", "autotools"]): + risk_factors.append({type: "BUILD_SYSTEM", change: change}) + IF change.contains(["security", "cve", "vulnerability"]): + risk_factors.append({type: "SECURITY", change: change, priority: "HIGH"}) +``` + +### Conflict Resolution Decision Tree +```pseudocode +FUNCTION resolve_conflict(file_path, conflict_content): + conflict_type = analyze_conflict_type(conflict_content) + + SWITCH conflict_type: + CASE "SECURITY_FIX": + // Always accept upstream security fixes + RETURN accept_upstream(conflict_content) + + CASE "BUILD_SYSTEM_CHANGE": + // Need to update Visual Studio projects + upstream_changes = extract_upstream_changes(conflict_content) + RETURN combine_with_windows_build_system(upstream_changes) + + CASE "PROCESS_MANAGEMENT": + // Unix fork/exec vs Windows CreateProcess + IF contains_fork_or_exec(conflict_content): + RETURN wrap_with_platform_ifdef(conflict_content) + ELSE: + RETURN analyze_compatibility(conflict_content) + + CASE "AUTHENTICATION": + // PAM/Kerberos vs Windows auth + RETURN preserve_windows_auth_with_upstream_features(conflict_content) + + CASE "CONFIGURATION": + // config.h vs config.h.vs changes + new_defines = extract_new_defines(conflict_content) + RETURN update_config_h_vs(new_defines) + + DEFAULT: + // Use historical pattern matching + similar_resolutions = find_similar_conflicts(file_path, conflict_content) + RETURN apply_similar_resolution_pattern(similar_resolutions[0]) +``` + +## Information Sources and Analysis + +### Primary References +- [Upstream release notes](https://www.openssh.com/releasenotes.html) - Pay special attention when merging new versions +- [Previous merge PRs](./research.instructions.md) - Review conflict resolution patterns +- Commit history and messages - Use `git log --oneline upstream/` to understand changes +- Local repository file comparison - Use 3-way diff tools + +### Analysis Commands +```pwsh +# View commit details for understanding changes +git show + +# Compare files between branches +git diff upstream-pwsh/latestw_all upstream/ -- +``` + +## Conflict Resolution Strategies + +### 1. Taking Upstream Changes +**When to use:** Security fixes, bug fixes, feature improvements that don't conflict with Windows functionality. + +```c +// Example: Accept upstream security patch +<<<<<<< HEAD +// Windows-specific code +======= +// New upstream security fix +>>>>>>> upstream/V_X_Y_PZ +``` +**Resolution:** Take the upstream change completely. + +### 2. Combining Changes with Preprocessor Directives +**When to use:** Upstream changes that conflict with Windows-specific functionality but both are needed. + +```c +// Example: Combining platform-specific implementations +#ifdef WINDOWS + // Windows-specific implementation + return windows_specific_function(); +#else + // Upstream Unix implementation + return unix_specific_function(); +#endif /* WINDOWS */ +``` + +### 3. Excluding Changes with #ifndef WINDOWS +**When to use:** Upstream changes are not applicable to Windows or would break Windows functionality. + +```c +// Example: Excluding Unix-only functionality +#ifndef WINDOWS + // Unix-only code that doesn't apply to Windows + setup_unix_specific_feature(); +#endif /* !WINDOWS */ +``` + +## Automated Conflict Resolution Patterns + +### Pattern 1: Platform-Specific Code Wrapping +```c +// Input conflict: +<<<<<<< HEAD +void windows_specific_function() { + // Windows implementation +} +======= +void unix_specific_function() { + // New upstream Unix implementation +} +>>>>>>> upstream/version + +// AI Resolution: +#ifdef WINDOWS +void windows_specific_function() { + // Windows implementation +} +#else +void unix_specific_function() { + // New upstream Unix implementation +} +#endif /* WINDOWS */ +``` + +### Pattern 2: Configuration File Updates +```pseudocode +FUNCTION update_config_h_vs(upstream_config_changes): + config_vs_path = "./contrib/win32/openssh/config.h.vs" + current_config = read_file(config_vs_path) + + FOR EACH define IN upstream_config_changes: + IF define.is_windows_compatible(): + IF define NOT IN current_config: + add_define_to_config_vs(define, determine_windows_value(define)) + ELSE: + // Add comment explaining why it's not included + add_comment_to_config_vs(f"// {define.name} - Unix only, not applicable to Windows") + + write_file(config_vs_path, current_config) +``` + +### Pattern 3: Build System Synchronization +```pseudocode +FUNCTION sync_build_system(): + makefile_changes = get_makefile_changes() + + FOR EACH binary IN makefile_changes.new_binaries: + IF binary.is_windows_applicable(): + create_visual_studio_project(binary) + add_to_solution_file(binary) + ELSE: + log(f"Skipping {binary} - Unix only") + + FOR EACH binary IN makefile_changes.removed_binaries: + remove_visual_studio_project(binary) + remove_from_solution_file(binary) + + FOR EACH source_file IN makefile_changes.new_source_files: + target_project = determine_target_project(source_file) + add_source_to_project(target_project, source_file) +``` + +## Common Conflict Patterns + +### File System Operations +- **Fork/exec calls** → Use Windows process creation APIs +- **Signal handling** → Use Windows event mechanisms +- **File permissions** → Adapt to Windows ACL model + +### Build System Changes +- **Makefile additions** → Update Visual Studio project files +- **New dependencies** → Check Windows compatibility +- **Compiler flags** → Translate to MSVC equivalents + +### Configuration Changes +- **New config options** → Add to `./contrib/win32/openssh/config.h.vs` +- **Feature detection** → Verify Windows support +- **Default values** → Adjust for Windows environment + +## Resolution Workflow + +### For Each Conflict: +1. **Analyze the change** + ```bash + git show upstream/ -- + ``` + +2. **Check previous resolutions** + - Search previous merge PRs for similar conflicts + - Look for patterns in Windows-specific handling + +3. **Choose resolution strategy** + - Upstream change: Complete replacement + - Combined: Add preprocessor directives + - Excluded: Use `#ifndef WINDOWS` + +4. **Test the resolution** + - Ensure code compiles + - Verify simple ssh connection to local host works + - Check that upstream functionality is preserved where applicable + +5. **Document the decision** + - Add comments explaining the Windows-specific handling + - Note in commit message why this approach was chosen + +## Build Validation Automation + +### Iterative Build and Fix Process +```pseudocode +FUNCTION automated_build_fix(): + MAX_ITERATIONS = 10 + iteration = 0 + + WHILE iteration < MAX_ITERATIONS: + build_result = attempt_build() + + IF build_result.success: + RETURN SUCCESS + + errors = parse_build_errors(build_result.output) + fixes_applied = [] + + FOR EACH error IN errors: + fix = determine_fix_strategy(error) + IF fix: + apply_fix(fix) + fixes_applied.append(fix) + + IF fixes_applied.empty(): + RETURN MANUAL_INTERVENTION_REQUIRED + + commit_build_fixes(fixes_applied, f"Build fixes iteration {iteration + 1}") + iteration += 1 + + RETURN MAX_ITERATIONS_EXCEEDED + +FUNCTION determine_fix_strategy(error): + SWITCH error.type: + CASE "MISSING_INCLUDE": + RETURN add_windows_include(error.missing_header) + CASE "UNDEFINED_FUNCTION": + RETURN add_windows_equivalent(error.function_name) + CASE "MISSING_DEFINE": + RETURN add_to_config_h_vs(error.define_name) + CASE "MISSING_SOURCE_FILE": + RETURN add_source_to_project(error.file_name) + DEFAULT: + RETURN null +``` + +## Testing Automation Framework + +### Automated Test Execution +```pseudocode +FUNCTION execute_test_suite(): + test_results = {} + + // Phase 1: Build verification + test_results["build"] = verify_build_artifacts() + IF NOT test_results["build"].success: + RETURN test_results + + // Phase 2: Service setup + test_results["service_setup"] = setup_ssh_service() + IF NOT test_results["service_setup"].success: + RETURN test_results + + // Phase 3: Basic connectivity + test_results["connectivity"] = test_basic_ssh_connection() + + // Cleanup + cleanup_test_environment() + + RETURN test_results + +FUNCTION generate_test_report(test_results): + report = "# Automated Test Results\n\n" + + FOR EACH category, result IN test_results: + status = result.success ? "✅ PASS" : "❌ FAIL" + report += f"## {category}: {status}\n" + + IF NOT result.success: + report += f"Error: {result.error}\n" + report += f"Suggested Fix: {result.suggested_fix}\n" + + RETURN report +``` + +## Error Recovery Strategies + +### Automatic Rollback Points +```pseudocode +FUNCTION create_checkpoint(phase_name): + current_commit = get_current_commit_hash() + checkpoints[phase_name] = current_commit + tag_commit(f"checkpoint-{phase_name}", current_commit) + +FUNCTION rollback_to_checkpoint(phase_name): + IF phase_name IN checkpoints: + reset_to_commit(checkpoints[phase_name]) + RETURN SUCCESS + ELSE: + RETURN CHECKPOINT_NOT_FOUND + +// Usage in main workflow +create_checkpoint("pre-merge") +execute_merge() + +IF merge_conflicts_too_complex(): + rollback_to_checkpoint("pre-merge") + request_manual_intervention() +``` + +### Conflict Complexity Assessment +```pseudocode +FUNCTION assess_conflict_complexity(conflicts): + complexity_score = 0 + + FOR EACH conflict IN conflicts: + // File-based scoring + IF conflict.file.ends_with(".c", ".h"): + complexity_score += 2 + IF conflict.file.contains("auth", "pam", "kerberos"): + complexity_score += 5 + IF conflict.file == "config.h": + complexity_score += 3 + + // Content-based scoring + lines_in_conflict = conflict.content.split('\n').length + complexity_score += lines_in_conflict * 0.1 + + // Pattern-based scoring + IF conflict.content.contains("fork", "exec", "signal"): + complexity_score += 10 + IF conflict.content.contains("WIN32", "WINDOWS", "#ifdef"): + complexity_score -= 2 // Already has platform guards + + IF complexity_score > 50: + RETURN "HIGH_COMPLEXITY" + ELIF complexity_score > 20: + RETURN "MEDIUM_COMPLEXITY" + ELSE: + RETURN "LOW_COMPLEXITY" +``` + +## Anti-Patterns to Avoid + +### ❌ Don't Remove Upstream Code +```c +// WRONG: This will cause future merge conflicts +// Completely removing upstream additions +``` + +### ❌ Don't Modify Upstream Logic Without Guards +```c +// WRONG: Modifying upstream code without preprocessor protection +upstream_function_with_windows_modifications(); +``` + +### ✅ Do Use Preprocessor Guards +```c +// CORRECT: Preserve upstream code with conditional compilation +#ifdef WINDOWS + windows_alternative(); +#else + upstream_function(); +#endif +``` + +## Progress Tracking and Reporting + +### Automated Progress Updates +```pseudocode +FUNCTION update_progress(phase, status, details): + progress_entry = { + timestamp: current_timestamp(), + phase: phase, + status: status, // SUCCESS, FAILURE, IN_PROGRESS + details: details, + commit_hash: get_current_commit_hash() + } + + append_to_progress_log(progress_entry) + + IF status == "FAILURE": + generate_failure_report(phase, details) + suggest_recovery_actions(phase, details) + +FUNCTION generate_merge_summary(): + summary = { + total_conflicts: count_resolved_conflicts(), + build_iterations: count_build_fix_iterations(), + test_results: get_final_test_results(), + time_elapsed: calculate_total_time(), + commits_created: count_commits_since_start(), + complexity_rating: assess_overall_complexity() + } + + RETURN format_summary_report(summary) +``` + +## Integration with Development Workflow + +### Pull Request Preparation +```pseudocode +FUNCTION prepare_pull_request(): + // Generate comprehensive commit history + commit_history = get_commits_since_branch_creation() + + // Create PR description + pr_description = f""" +# Merge OpenSSH {target_version} to Windows Fork + +## Summary +{generate_merge_summary()} + +## Conflict Resolutions +{generate_conflict_resolution_summary()} + +## Testing Results +{generate_test_report(final_test_results)} + +## Files Modified +{list_modified_files()} + +## Breaking Changes +{identify_breaking_changes()} +""" + + RETURN { + title: f"Merge upstream OpenSSH {target_version}", + description: pr_description, + labels: ["upstream-merge", determine_complexity_label()], + assignees: get_default_reviewers() + } +``` + +## Commit Message Template + +``` +Resolve merge conflicts for + +Major conflict resolutions: +- : Combined upstream with Windows using #ifdef +- : Excluded upstream with #ifndef WINDOWS due to +- : Accepted upstream completely + +Reasoning: +``` + +## Troubleshooting + +### If Unsure About a Conflict: +1. Check if the upstream change addresses a CVE or security issue (prioritize) +2. Look for similar code patterns elsewhere in the Windows codebase +3. Consult the OpenSSH-portable issue tracker for context +4. When in doubt, use conditional compilation to preserve both approaches + +### Testing Your Resolution: +- Build the project after each major conflict resolution +- Run ssh connection to local host to ensure basic functionality +- Check that removed functionality wasn't critical to Windows operation \ No newline at end of file diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md new file mode 100644 index 000000000000..34f4b413a6b5 --- /dev/null +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -0,0 +1,280 @@ +--- +applyTo: "**/*" +--- + +# OpenSSH-Portable: Merge From Upstream Instructions + +## Overview +This documentation provides comprehensive instructions for merging OpenBSD's OpenSSH-Portable changes into the PowerShell team's Windows-compatible fork. It is designed to be used by both human developers and AI agents to complete a full merge process that results in a ready-to-merge Pull Request. + +## Prerequisites +Ensure the following tools are installed and configured before proceeding: +- **Git** +- **PowerShell** +- **Visual Studio** with: + - Latest C/C++ development tools + - Latest Windows 10/11 SDK + +## Process Overview +The merge process uses a **chunked approach** to reduce complexity and improve success rates. Instead of merging entire upstream versions at once, commits are processed in small batches ending with commits that have CI runs. Each batch is built and optionally validated before proceeding to the next. + +The process consists of several interconnected phases: + +1. **[Setup Phase](#setup-phase)** - Repository configuration and preparation +2. **[Research Phase](#research-phase)** - Understanding changes and conflicts +3. **[Merge Phase](#merge-phase)** - Performing chunked commit merging +4. **[Build Phase](#build-phase)** - Resolving compilation issues +5. **[Testing Phase](#testing-phase)** - Validating functionality +6. **[Submission Phase](#submission-phase)** - Creating the Pull Request + +## Setup Phase + +**📖 Detailed Instructions:** [Setup Instructions](../setup.instructions.md) + +**Quick Overview:** +1. Clone your fork of the openssh-portable repository +2. Configure upstream remotes (PowerShell team fork + original OpenSSH) +3. Fetch latest changes + +## Research Phase + +**📖 Detailed Instructions:** [Research Instructions](./research.instructions.md) + +**Key Resources:** +- **Upstream Release Notes:** [OpenSSH Release Notes](https://www.openssh.com/releasenotes.html) +- **Previous Merge PRs:** such as https://github.com/PowerShell/openssh-portable/pull/737 + +## Merge Phase + +### Initial Preparation +1. **Checkout base branch:** + ```pwsh + git checkout upstream-pwsh/latestw_all + ``` + +2. **Verify baseline build** + **(📖 Detailed Instructions:** [Build Instructions](../build.instructions.md)): + ```pwsh + Import-Module .\contrib\win32\openssh\OpenSSHBuildHelper.psm1 -Force + Start-OpenSSHBuild -Configuration Release -NativeHostArch x64 + ``` + +3. **Configure git:** + ```pwsh + git config core.editor true + ``` + +3. **Create merge branch:** + ```pwsh + git checkout -b merge-v- + # Example: merge-v9.8-20241010 + ``` + +### Perform Merge with Grouped Commits +4. **Identify merge range and group commits:** + Use .\tools\Get-CommitGroups.ps1 with `-FirstChunkOnly -GroupByCIPresence` + ```pwsh + # Find the last upstream tag in the fork (this is the starting point for the next merge) + # Call .\tools\Get-CommitGroups.ps1 -GitHubTag -FirstChunkOnly -GroupByCIPresence + # This gets commits ending with any commit that has CI runs (not just successful CI) + # Example output: + # { + # "ChunkNumber": 1, + # "StartCommit": "609fe2c", + # "EndCommit": "6fb728d", + # "StartCommitFull": "609fe2cae2459d721ac11d23cd27b8a94397ef3c", + # "EndCommitFull": "6fb728df50c1afd338cb0223a84ce24579577eff", + # "CommitCount": 57, + # "StartMessage": "upstream: rework the text for -3 to make it clearer what default", + # "EndMessage": "Run all tests on Cygwin again." + # } + + # Print the commit batch details for verification + Write-Host "Processing batch: $($result.StartCommit)..$($result.EndCommit)" + Write-Host "Start: $($result.StartMessage)" + Write-Host "End: $($result.EndMessage)" + Write-Host "End Commit CI Status: $($result.EndCommit has CI runs)" + + # After completing steps below, get next batch: + # Call .\tools\Get-CommitGroups.ps1 -StartCommit -FirstChunkOnly -GroupByCIPresence + # Continue this process untiland + # follow the below steps to completion until the upstream's HEAD has been successfully merged + ``` + +5. **Execute chunked merge (commit-by-commit within the chunk):** + + **Important:** Even though commits are *grouped* into chunks for planning/CI boundaries, **cherry-pick each commit individually** within the chunk and **build after each commit**. This catches Windows-specific build breaks early and makes it clear which upstream commit introduced a regression. + + ```pwsh + # For each chunk (start..end), enumerate and cherry-pick commits one at a time. + # Then build after EACH commit. + # + # Example for a single chunk: + $chunkStart = $result.StartCommitFull + $chunkEnd = $result.EndCommitFull + $commits = git rev-list --reverse "$chunkStart^..$chunkEnd" + + foreach ($commit in $commits) { + Write-Host "Cherry-picking commit: $commit" + git cherry-pick $commit + + # Build after every commit so we can attribute failures precisely. + # Prefer the MCP build helper if available; otherwise use Start-OpenSSHBuild. + .\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 + } + ``` + +7. **Resolve merge conflicts (per commit):** + **📖 Detailed Instructions** ([Merge Details](./merge-details.instructions.md)): + + - Resolve conflicts for the current commit only + - Use three-way comparison tools + - Follow established Windows compatibility patterns + - Reference previous merge PRs for similar conflicts + +8. **Continue cherry-picking after resolution:** + ```pwsh + # After resolving conflicts for current commit + git add . + git cherry-pick --continue + + # Then continue with remaining commits in batch + # Repeat process for next batch if needed + ``` + +9. **Build after completing the batch:** + ```pwsh + .\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 + + # If build fails, fix issues and rebuild + # Commit any build fixes separately + ``` + +10. **Validate if batch ended with successful CI:** + ```pwsh + # Check the end commit's CI status from Get-CommitGroups output + # If CI was successful, run validation: + .\.github\tools\Test-OpenSSHFunctionality.ps1 + + # If no CI or failed CI, skip validation (build success is sufficient) + ``` + +11. **Provide summary and get approval:** + - Summarize batch changes, conflicts resolved, build status, validation status + - Wait for user approval before proceeding to next batch + - Document next steps (starting commit for next batch) +--- + +## Build Phase + +**📖 Detailed Instructions:** [Build Instructions](../build.instructions.md) + +7. **Initial build attempt:** + ```pwsh + Start-OpenSSHBuild -Configuration Release -NativeHostArch x64 + ``` + +8. **Resolve compilation errors (iterative process):** + + **Common Areas to Check:** + - **config.h.vs updates:** New preprocessor definitions + - **Function signatures:** Windows equivalents for Unix functions + - **Build system changes:** Makefile vs Visual Studio projects + - **New dependencies:** Windows compatibility verification + +9. **Update Visual Studio projects:** + - Check Makefile for added/removed binaries + - Create/update .vcxproj files as needed + - Update Win32-OpenSSH.sln solution file + - Ensure Windows-applicable binaries only + +10. **Commit build fixes:** + ```pwsh + git commit -m "Fix compilation errors for + + Changes: + - Updated config.h.vs with + - Added Windows equivalent for + - Updated project files for " + ``` + +--- + +## Testing Phase + +**📖 Detailed Instructions:** [Testing Instructions](../testing.instructions.md) + +11. **Basic functionality test:** + ```pwsh + # Set up SSH service (see testing.instructions.md) + ssh.exe @localhost + ``` + +12. **Troubleshoot connection issues:** + - Enable verbose logging + - Check service configuration + - Use debugger if necessary + - Verify certificate/key handling + +13. **Commit any test fixes:** + ```pwsh + git commit -m "Fix runtime issues for + + Issues resolved: + - " + ``` + +--- + +## Submission Phase + +### Creating the Pull Request +14. **Push to fork:** + ```pwsh + git push origin merge-v- + ``` + +15. **Create Pull Request:** + - Target: `PowerShell/openssh-portable:latestw_all` + - Title: `Merge upstream OpenSSH ` + - Include comprehensive description of changes and resolutions + +16. **Address CI/test failures:** + - Monitor automated tests + - Fix any Windows-specific test failures + - Ensure all checks pass + +17. **Request review:** + - Tag appropriate PowerShell team reviewers + - Provide context for complex conflict resolutions + +--- + +## Success Criteria + +**The merge is complete when:** +- [ ] All merge conflicts resolved with documented reasoning +- [ ] Solution builds successfully on Windows +- [ ] Basic SSH connection test passes +- [ ] All CI tests pass +- [ ] PR approved and ready for merge + +--- + +## AI Agent Resources + +AI agents should utilize these additional resources: + +- **[Reference Analysis](./research.instructions.md)** - Intelligence gathering protocols +- **[Merge Details](./merge-details.instructions.md)** - Comprehensive merge process, conflict resolution, and automation algorithms +- **[Testing Instructions](../testing.instructions.md)** - Detailed validation procedures + +**For AI Agents:** +1. **Start with this overview** - Follow the phases and track progress commit-by-commit +2. **Follow decision trees** - Refer to AI agent instructions for algorithmic guidance +3. **Document everything** - Maintain detailed commit messages and resolution rationale +4. **Use automated testing** - Leverage provided test scripts for validation +5. **Escalate when needed** - If complexity exceeds capabilities, document and request human intervention + +**Success Criteria Check:** +The merge process is successful when an AI agent can complete all phases independently and produce a Pull Request that meets all quality standards without human intervention. diff --git a/.github/instructions/merge/research.instructions.md b/.github/instructions/merge/research.instructions.md new file mode 100644 index 000000000000..f8b8c5032d5c --- /dev/null +++ b/.github/instructions/merge/research.instructions.md @@ -0,0 +1,106 @@ +--- +applyTo: "**/*" +--- + +# References for AI Agents + +## AI Agent Instructions +This file provides reference materials and links that AI agents should review before and during the merge process. Each section contains specific guidance on what to look for and how to use the information. + +## Upstream Release Notes +**URL:** https://www.openssh.com/releasenotes.html + +**AI Agent Task:** +1. Navigate to the release notes page +2. Focus ONLY on the latest release section (at the top of the page) +3. Look for these specific types of changes that require Windows compatibility work: + - Signal handling modifications + - File handling changes + - Inter-process communication (IPC) updates + - New system calls or POSIX-specific functionality + - Changes to build system or dependencies + - Security fixes that might affect Windows implementations + +**Example Analysis:** +``` +If release notes mention "Added support for new signal handling in sshd", +the AI should flag this as requiring Windows event mechanism adaptation. +``` + +## Previous Merge Pull Requests + +**AI Agent Task:** Review these PRs in order of recency for conflict resolution patterns: + +1. **Most Recent:** https://github.com/PowerShell/openssh-portable/pull/737 +2. https://github.com/PowerShell/openssh-portable/pull/703 +3. https://github.com/PowerShell/openssh-portable/pull/684 +4. https://github.com/PowerShell/openssh-portable/pull/657 +5. https://github.com/PowerShell/openssh-portable/pull/626 +6. https://github.com/PowerShell/openssh-portable/pull/577 +7. https://github.com/PowerShell/openssh-portable/pull/504 +8. https://github.com/PowerShell/openssh-portable/pull/351 + +**What to Extract from Each PR:** +1. **Commit Messages:** Look for commits added AFTER the initial merge commit +2. **File Patterns:** Note which files commonly have conflicts +3. **Resolution Strategies:** Document how specific types of conflicts were resolved +4. **Build Fixes:** Note compilation issues and their solutions +5. **Test Failures:** Understand common CI/test failures and fixes + +**Key Contributors to Follow:** +- Regular PowerShell/OpenSSH-Portable contributors +- Their commit patterns and resolution strategies +- Comments and review feedback + +## Upstream Repository Analysis + +**Commands for AI Agent:** +```pwsh +# Get commit history for target version +git log --oneline upstream/ --since="" + +# Analyze specific commits +git show + +# Compare branches +git diff upstream-pwsh/latestw_all upstream/ +``` + +## Windows-Specific Knowledge Base + +### Common Conflict Areas +1. **Process Management:** fork() vs CreateProcess() +2. **Signal Handling:** Unix signals vs Windows events +3. **File Permissions:** POSIX permissions vs Windows ACLs +4. **Path Handling:** Unix paths vs Windows paths +5. **Build System:** Makefile vs Visual Studio projects + +### Resolution Pattern Library +```c +// Pattern 1: Platform-specific implementation +#ifdef WINDOWS + // Windows implementation +#else + // Unix implementation +#endif + +// Pattern 2: Exclude Unix-only features +#ifndef WINDOWS + // Unix-only code +#endif + +// Pattern 3: Windows compatibility layer +#ifdef WINDOWS + #include "win32compat.h" +#endif +``` + +## Decision Matrix for AI Agents + +| Conflict Type | Resolution Strategy | Example | +|---------------|-------------------|---------| +| Security Fix | Accept upstream completely | CVE patches | +| Build System | Update VS project files | New source files | +| System Calls | Add Windows equivalent | fork() → CreateProcess() | +| Configuration | Update config.h.vs | New preprocessor defines | +| Test Code | Platform-specific guards | Unix-only tests | diff --git a/.github/instructions/repository-overview.instructions.md b/.github/instructions/repository-overview.instructions.md new file mode 100644 index 000000000000..aa343214f394 --- /dev/null +++ b/.github/instructions/repository-overview.instructions.md @@ -0,0 +1,131 @@ +--- +applyTo: "**/*" +--- + +# Repository Structure and Windows Compatibility Layer + +## Overview + +This repository is a **downstream fork** of [openssh/openssh-portable](https://github.com/openssh/openssh-portable) maintained by the PowerShell team to provide Windows compatibility for OpenSSH. + +## Repository Organization + +### Upstream Code (Base Directory) +The root of the repository contains the upstream OpenSSH code: +- Core SSH implementation files (`.c`, `.h`) +- Unix/POSIX-focused codebase +- Maintained to stay close to upstream for easier merging + +### Windows Compatibility Layer + +#### Primary Windows Code Locations + +**1. `.\contrib\win32\openssh\`** +- Visual Studio project files (`.vcxproj`, `.sln`) +- Windows-specific build configuration +- Project organization for Windows builds + +**2. `.\contrib\win32\win32compat\`** +- Windows compatibility implementation layer +- Provides Windows equivalents for POSIX functions +- Contains platform abstraction code + +#### Compatibility Strategy + +The Windows port follows a **separation of concerns** approach to minimize divergence from upstream: + +1. **Preferred: Compatibility Layer** + - Windows-specific implementations prefixed with `w32_` + - Example: `w32_mkdir()`, `w32_stat()`, `w32_open()` + - Macro redefinition in headers to redirect POSIX calls + - Located in `contrib\win32\win32compat\` + +2. **When Necessary: Conditional Compilation** + - Use `#ifdef WINDOWS` blocks in upstream files + - Keep Windows-specific code minimal and well-documented + - Only when compatibility layer approach is insufficient + +**Example Pattern:** +```c +// In win32compat header (e.g., sys/stat.h) +int w32_mkdir(const char *pathname, unsigned short mode); +#undef mkdir // Clear any existing definition +#define mkdir w32_mkdir // Redirect to Windows implementation + +// In upstream code - no changes needed +mkdir(path, 0700); // Automatically uses w32_mkdir on Windows +``` + +## Special Case: SSH-Agent + +### Important: Separate Windows Implementation + +The **ssh-agent** is a special case that **does not follow** the standard compatibility layer pattern: + +- **Windows ssh-agent location**: `.\contrib\win32\win32compat\ssh-agent\` +- Built from **completely separate code** from the upstream ssh-agent +- Uses Windows-native APIs and service architecture + +### Implications for Upstream Merges + +When merging changes to `ssh-agent.c` or related agent files from upstream: + +1. **Simple changes** (bug fixes, small improvements): + - Manually port functionality to `contrib\win32\win32compat\ssh-agent\` + - Adapt logic to Windows implementation + +2. **Complex changes** (architectural changes, new features): + - Document as TODO in merge commit + - Flag for Windows team review + - May require significant redesign work + +3. **Do NOT**: + - Directly apply upstream ssh-agent patches to Windows version + - Assume one-to-one code correspondence + - Merge without understanding Windows implementation differences + +### Other Binaries + +All other OpenSSH binaries (ssh, sshd, scp, sftp, etc.) follow the standard compatibility layer approach and can be merged more directly from upstream with appropriate Windows compatibility adjustments. + +## Best Practices for Merging + +### 1. Minimize Direct Modifications +- Prefer extending compatibility layer over adding `#ifdef WINDOWS` blocks +- Keep upstream code as clean as possible + +### 2. Document Windows-Specific Changes +- Clear comments explaining why Windows needs different approach +- Reference related compatibility layer functions + +### 3. Test on Windows +- Always build and test on Windows after merging +- Use provided automation tools in `.github\tools\` +- Verify both functionality and build process + +### 4. Upstream Alignment +- Track which upstream commits have been merged +- Maintain clear merge history +- Document any deviations from upstream behavior + +## Key Files to Know + +### Build System +- `contrib\win32\openssh\Win32-OpenSSH.sln` - Main Visual Studio solution +- `contrib\win32\openssh\*.vcxproj` - Individual project files +- `contrib\win32\openssh\OpenSSHBuildHelper.psm1` - Build helper module + +### Compatibility Headers +- `contrib\win32\win32compat\inc\sys\*` - POSIX header replacements +- `contrib\win32\win32compat\inc\*.h` - Windows compatibility declarations +- `contrib\win32\win32compat\inc\crtheaders.h` - CRT header mappings + +### Compatibility Implementation +- `contrib\win32\win32compat\*.c` - Windows function implementations +- `contrib\win32\win32compat\ssh-agent\*` - Separate Windows ssh-agent + +## Getting Help + +- **Build issues**: See [build.instructions.md](./build.instructions.md) +- **Merge conflicts**: See [merge/merge-details.instructions.md](./merge/merge-details.instructions.md) +- **Testing**: See [testing.instructions.md](./testing.instructions.md) diff --git a/.github/instructions/setup.instructions.md b/.github/instructions/setup.instructions.md new file mode 100644 index 000000000000..3081ae3cfdd2 --- /dev/null +++ b/.github/instructions/setup.instructions.md @@ -0,0 +1,86 @@ +--- +applyTo: "**/*" +--- + +# Repository Setup Instructions for AI Agents + +## Initial Repository Setup + +### Step 1: Clone Your Fork +```pwsh +# Replace 'your-username' with actual GitHub username +git clone https://github.com/your-username/openssh-portable.git +cd openssh-portable +``` + +### Step 2: Add Upstream Repositories +```pwsh +# Add PowerShell team's fork as upstream-pwsh +git remote add upstream-pwsh https://github.com/PowerShell/openssh-portable.git + +# Add original OpenSSH repository as upstream +git remote add upstream https://github.com/openssh/openssh-portable.git +``` + +### Step 3: Verify Remote Configuration +```pwsh +git remote -v +# Expected output: +# origin https://github.com/your-username/openssh-portable.git (fetch) +# origin https://github.com/your-username/openssh-portable.git (push) +# upstream https://github.com/openssh/openssh-portable.git (fetch) +# upstream https://github.com/openssh/openssh-portable.git (push) +# upstream-pwsh https://github.com/PowerShell/openssh-portable.git (fetch) +# upstream-pwsh https://github.com/PowerShell/openssh-portable.git (push) +``` + +### Step 4: Initial Fetch +```pwsh +git fetch --all +``` + +## Branch Strategy + +### Understanding the Branch Structure +- **upstream-pwsh/latestw_all**: Main Windows-compatible branch (base for merges) +- **upstream/master**: Latest upstream OpenSSH development +- **upstream/V_X_Y_PZ**: Tagged releases (merge targets) + +### Verification Commands +```pwsh +# List all branches +git branch -r + +# Check current branch +git branch +``` + +### Step 5: Clone VCPkg Repository +```pwsh +# In the same parent directory as openssh-portable +git clone https://github.com/Microsoft/vcpkg.git +``` + +### Step 6: Setup VCPkg Integration with MSBuild +```pwsh +cd vcpkg +.\bootstrap-vcpkg.bat +.\vcpkg.exe integrate install +``` + +## AI Agent Checklist + +Before proceeding to merge: +- [ ] Repository cloned successfully +- [ ] All three remotes configured (origin, upstream, upstream-pwsh) +- [ ] Can fetch from all remotes without errors +- [ ] Can see upstream-pwsh/latestw_all branch +- [ ] Can see upstream target version/branch +- [ ] Working directory is clean (`git status` shows no uncommitted changes) + +## Troubleshooting + +### Common Issues +1. **Authentication errors**: Ensure GitHub credentials are configured +2. **Network issues**: Check proxy settings if behind corporate firewall +3. **Branch not found**: Verify branch/tag names are correct diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md new file mode 100644 index 000000000000..7aa715edeab2 --- /dev/null +++ b/.github/instructions/testing.instructions.md @@ -0,0 +1,248 @@ +--- +applyTo: "**/*" +--- + +# Testing Instructions for AI Agents + +## Overview +This document provides comprehensive testing procedures for validating OpenSSH-Portable merges on Windows. Testing should be performed after successful compilation to ensure functionality is preserved. + +## Automated Testing with MCP Tools (Recommended) + +### Using Test-OpenSSHFunctionality Tool + +The repository includes an MCP tool that automates end-to-end functional testing of OpenSSH on Windows. + +```pwsh +# Run automated functional test (requires Administrator privileges) +.\.github\tools\Test-OpenSSHFunctionality.ps1 + +# Test with specific configuration +.\.github\tools\Test-OpenSSHFunctionality.ps1 -Configuration Debug -Architecture x64 + +# Skip firewall configuration if rules already exist +.\.github\tools\Test-OpenSSHFunctionality.ps1 -SkipFirewall +``` + +**What the tool does:** +1. Verifies Administrator privileges +2. Creates a temporary test user with random password +3. Installs and starts the SSH service +4. Configures Windows Firewall (unless -SkipFirewall is used) +5. Tests SSH connection with password authentication +6. Executes "echo hello world" command via SSH +7. Cleans up all resources (user, service, firewall rule) + +**Expected output on success:** +``` +=== OpenSSH Functionality Test === +[1/6] Checking Administrator privileges... +✓ Running with Administrator privileges +[2/6] Creating temporary test user... +✓ Created test user: openssh_test_1234 +[3/6] Installing SSH service... +✓ SSH service installed successfully +[4/6] Starting SSH service... +✓ SSH service started successfully +[5/6] Configuring Windows Firewall... +✓ Firewall rule created +[6/6] Testing SSH connection... +✓ SSH connection successful + Command output: hello world + +=== Cleanup === +✓ SSH service uninstalled +✓ Firewall rule removed +✓ Test user removed + +=== Test Summary === +Status: PASSED +``` + +**The tool returns a structured result object with:** +- `Success`: Boolean indicating overall test success +- `ServiceInstalled`: Whether service installation succeeded +- `ServiceStarted`: Whether service started successfully +- `ConnectionSuccessful`: Whether SSH connection test passed +- `CommandOutput`: Output from the test command +- `TestUser`: Name of the temporary test user created +- `Errors`: Array of any errors encountered +- `Message`: Summary message + +## Manual Testing Procedures + +If you need to perform manual testing or troubleshoot specific issues, follow these procedures: + +### Prerequisites Check +```pwsh +# Verify Windows version compatibility +Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion + +# Check if running as Administrator - REQUIRED for service installation +if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) +{ + Write-Error "Administrative privileges are REQUIRED for service installation and testing." + Write-Host "Please restart PowerShell or VS Code as Administrator and try again." -ForegroundColor Yellow + Write-Host "To elevate: Right-click PowerShell/VS Code -> 'Run as Administrator'" -ForegroundColor Yellow + exit 1 +} + +Write-Host "✓ Running with Administrator privileges" -ForegroundColor Green + +# Verify build artifacts exist +$buildPath = ".\contrib\win32\openssh\x64\Release" +if (-not (Test-Path "$buildPath\sshd.exe") -or -not (Test-Path "$buildPath\ssh.exe")) { + Write-Error "Build artifacts not found. Please build the project first." + exit 1 +} + +Write-Host "✓ Build artifacts verified" -ForegroundColor Green +``` + +### Test Environment Setup + +### Service Installation and Configuration + +#### Step 1: Install SSH Service +```pwsh +# Navigate to build directory +cd .\contrib\win32\openssh\x64\Release + +# Install SSH server service with PowerShell script +.\install-sshd.ps1 + +# Verify service installation +Get-Service sshd -ErrorAction SilentlyContinue | Select-Object Name, Status, StartType +``` + +#### Step 2: Configure SSH Service +```pwsh +# Start SSH service +Start-Service sshd + +# Verify service is running +Get-Service sshd | Select-Object Name, Status +``` + +#### Step 3: Configure Windows Firewall (if needed) +```pwsh +# Allow SSH through Windows Firewall +New-NetFirewallRule -DisplayName "SSH Server (sshd)" -Direction Inbound -Port 22 -Protocol TCP -Action Allow -ErrorAction SilentlyContinue +``` + +## Basic Functionality Tests + +### Test 1: SSH Client Connection +```pwsh +# Test local connection (most basic test) +$username = $env:USERNAME +$hostname = "localhost" + +Write-Host "Testing SSH connection: ssh $username@$hostname" + +# Basic connection test +.\ssh.exe $username@$hostname "echo 'SSH connection successful'" +``` + +**Expected Output:** +``` +SSH connection successful +``` + +## Error Diagnosis and Troubleshooting + +### Run SSH Server in Debug Mode +```pwsh +# Enable SSH daemon debug logging +Stop-Service sshd +.\sshd.exe -ddd + +# In another terminal, test connection with verbose client logging +.\ssh.exe -vvv $username@$hostname +``` + +### Common Issues and Solutions + +#### Issue 1: Service Won't Start +**Symptoms:** +- Service fails to start +- Event log shows service errors + +**Diagnosis:** +```pwsh +# Check event logs +Get-WinEvent -LogName System | Where-Object {$_.ProviderName -eq "Service Control Manager" -and $_.Id -eq 7034} | Select-Object -First 5 + +# Check sshd configuration +.\sshd.exe -T +``` + +**Common Solutions:** +- Verify configuration file syntax + +#### Issue 2: Connection Timeouts +**Symptoms:** +- SSH client hangs +- Connection timeout errors + +**Diagnosis:** +```pwsh +# Check network connectivity +Test-NetConnection -ComputerName localhost -Port 22 + +# Check Windows Firewall rules +Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*SSH*"} +``` + +## Success Criteria + +**Testing is successful when:** +- [ ] All expected executables are present after build (verified by Test-OpenSSHBuild.ps1) +- [ ] SSH service installs and starts without errors +- [ ] SSH connection with password authentication succeeds +- [ ] Test command executes successfully via SSH connection +- [ ] All resources cleaned up properly after testing + + +## AI Agent Guidelines + +1. **Use automated testing tools** whenever possible - prefer Test-OpenSSHFunctionality.ps1 over manual procedures +2. **Run tests incrementally** during the merge process, not just at the end +3. **Document any test failures** and their resolutions in commit messages +4. **Pay special attention to Windows-specific functionality** that might be affected by upstream changes +5. **Always verify cleanup** - ensure test users, services, and firewall rules are removed +6. **Report any new functionality** that needs additional testing procedures + +### Recommended Testing Workflow for AI Agents + +1. **After successful build**, run the automated functionality test: + ```pwsh + .\.github\tools\Test-OpenSSHFunctionality.ps1 + ``` + +2. **If test passes**, the merge is validated for basic SSH functionality + +3. **If test fails**, use manual procedures and debug mode to diagnose issues + +4. **Document results** in commit message or merge documentation + +## Manual Test Environment Cleanup + +If you ran manual tests instead of using the automated tool: + +```pwsh +# Clean up test environment +Stop-Service sshd -ErrorAction SilentlyContinue +cd .\contrib\win32\openssh\x64\Release +.\uninstall-sshd.ps1 + +# Remove firewall rule +Remove-NetFirewallRule -DisplayName "SSH Server (sshd)" -ErrorAction SilentlyContinue + +# Remove any test users manually created +Remove-LocalUser -Name "test_username" -ErrorAction SilentlyContinue + +Write-Host "Test environment cleaned up" +``` + +**Note:** The automated Test-OpenSSHFunctionality.ps1 tool handles all cleanup automatically, even on failure. diff --git a/.github/tools/Build-OpenSSH.ps1 b/.github/tools/Build-OpenSSH.ps1 new file mode 100644 index 000000000000..692559066f0e --- /dev/null +++ b/.github/tools/Build-OpenSSH.ps1 @@ -0,0 +1,254 @@ +<# +.SYNOPSIS + MCP wrapper tool that builds and verifies OpenSSH in a single operation. + +.DESCRIPTION + This script combines Start-OpenSSHBuild and Test-OpenSSHBuild into a single + convenient operation. It first builds OpenSSH using the specified configuration + and architecture, then tests that all expected artifacts were produced and + parses the build log for errors. + + This is the recommended tool for most build operations as it provides + comprehensive feedback in one step. + +.PARAMETER Configuration + Build configuration type. Valid values: 'Debug', 'Release' + Default: 'Release' + +.PARAMETER Architecture + Target architecture for the build. Valid values: 'x64', 'x86', 'ARM', 'ARM64' + Default: 'x64' + +.PARAMETER Clean + When specified, performs a clean build by deleting existing build artifacts first. + Default: false (incremental build) + +.PARAMETER NoOpenSSL + Build without OpenSSL support. + Default: false + +.PARAMETER OneCore + Build for Windows OneCore API subset. + Default: false + +.OUTPUTS + Returns a consolidated hashtable with: + - BuildSuccess: Boolean indicating if build succeeded + - TestSuccess: Boolean indicating if test passed + - OverallSuccess: Boolean indicating both build and test succeeded + - BuildExitCode: MSBuild exit code + - BuildMessage: Build status message + - TotalArtifacts: Count of artifacts found + - ExpectedArtifacts: Count of artifacts expected (14) + - ArtifactsMissing: Array of missing artifacts + - Errors: Array of parsed error objects + - Warnings: Array of parsed warning objects + - LogFile: Path to build log file + - BuildPath: Path to build output directory + - Message: Overall summary message + +.EXAMPLE + .\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 + + Performs an incremental release build for x64 and tests artifacts. + +.EXAMPLE + .\Build-OpenSSH.ps1 -Configuration Debug -Architecture x64 -Clean + + Performs a clean debug build for x64 and tests artifacts. + +.EXAMPLE + .\Build-OpenSSH.ps1 -Architecture ARM64 -OneCore + + Performs an incremental OneCore release build for ARM64 and tests artifacts. + +.NOTES + - This tool calls Start-OpenSSHBuild.ps1 and Test-OpenSSHBuild.ps1 + - Requires Visual Studio 2019 or later with C++ tools + - Requires Windows SDK 10.0.17763.0 or later + - Build artifacts output to: contrib\win32\openssh\{Architecture}\{Configuration}\ + - Build log written to: OpenSSH{Configuration}{Architecture}.log +#> + +param( + [Parameter(Mandatory=$false)] + [ValidateSet('Debug', 'Release')] + [string]$Configuration = 'Release', + + [Parameter(Mandatory=$false)] + [ValidateSet('x64', 'x86', 'ARM', 'ARM64')] + [string]$Architecture = 'x64', + + [Parameter(Mandatory=$false)] + [switch]$Clean, + + [Parameter(Mandatory=$false)] + [switch]$NoOpenSSL, + + [Parameter(Mandatory=$false)] + [switch]$OneCore +) + +$scriptRoot = $PSScriptRoot + +try { + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "OpenSSH Build & Test Tool (MCP)" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "Configuration: $Configuration" -ForegroundColor White + Write-Host "Architecture: $Architecture" -ForegroundColor White + Write-Host "Clean Build: $Clean" -ForegroundColor White + Write-Host "No OpenSSL: $NoOpenSSL" -ForegroundColor White + Write-Host "OneCore: $OneCore" -ForegroundColor White + Write-Host "========================================`n" -ForegroundColor Cyan + + # Step 1: Build + Write-Host "STEP 1: Building OpenSSH..." -ForegroundColor Cyan + Write-Host "----------------------------------------`n" -ForegroundColor Cyan + + $buildParams = @{ + Configuration = $Configuration + Architecture = $Architecture + } + + if ($Clean) { + $buildParams['Clean'] = $true + } + + if ($NoOpenSSL) { + $buildParams['NoOpenSSL'] = $true + } + + if ($OneCore) { + $buildParams['OneCore'] = $true + } + + $buildScriptPath = Join-Path $scriptRoot "Start-OpenSSHBuild.ps1" + $buildResult = & $buildScriptPath @buildParams + + if (-not $buildResult.Success) { + Write-Host "`n⚠ Build failed, skipping test" -ForegroundColor Yellow + + # Return build result with test skipped + $result = @{ + BuildSuccess = $false + TestSuccess = $false + OverallSuccess = $false + BuildExitCode = $buildResult.ExitCode + BuildMessage = $buildResult.Message + TotalArtifacts = 0 + ExpectedArtifacts = 14 + ArtifactsMissing = @() + Errors = @() + Warnings = @() + LogFile = $buildResult.LogFile + BuildPath = $buildResult.BuildPath + Message = "Build failed: $($buildResult.Message)" + } + + return $result + } + + # Step 2: Test + Write-Host "`nSTEP 2: Testing Build Artifacts..." -ForegroundColor Cyan + Write-Host "----------------------------------------`n" -ForegroundColor Cyan + + $testParams = @{ + Configuration = $Configuration + Architecture = $Architecture + LogFile = $buildResult.LogFile + } + + $testScriptPath = Join-Path $scriptRoot "Test-OpenSSHBuild.ps1" + $testResult = & $testScriptPath @testParams + + # Step 3: Consolidate results + $overallSuccess = $buildResult.Success -and $testResult.Success + + Write-Host "`n========================================" -ForegroundColor $(if ($overallSuccess) { "Green" } else { "Red" }) + Write-Host "OVERALL RESULT: $(if ($overallSuccess) { 'SUCCESS' } else { 'FAILED' })" -ForegroundColor $(if ($overallSuccess) { "Green" } else { "Red" }) + Write-Host "========================================" -ForegroundColor $(if ($overallSuccess) { "Green" } else { "Red" }) + + if ($overallSuccess) { + Write-Host "✓ Build succeeded" -ForegroundColor Green + Write-Host "✓ All $($testResult.ExpectedArtifacts) artifacts tested" -ForegroundColor Green + Write-Host "✓ No errors found" -ForegroundColor Green + } else { + if (-not $buildResult.Success) { + Write-Host "✗ Build failed with exit code $($buildResult.ExitCode)" -ForegroundColor Red + } + if ($testResult.ArtifactsMissing.Count -gt 0) { + Write-Host "✗ $($testResult.ArtifactsMissing.Count) artifacts missing" -ForegroundColor Red + } + if ($testResult.Errors.Count -gt 0) { + Write-Host "✗ $($testResult.Errors.Count) error(s) found" -ForegroundColor Red + } + } + + Write-Host "`nBuild artifacts: $($buildResult.BuildPath)" -ForegroundColor White + Write-Host "Build log: $($buildResult.LogFile)" -ForegroundColor White + + # Build consolidated message + $messageParts = @() + if ($buildResult.Success) { + $messageParts += "Build succeeded" + } else { + $messageParts += "Build failed" + } + + if ($testResult.Success) { + $messageParts += "all artifacts tested" + } else { + if ($testResult.ArtifactsMissing.Count -gt 0) { + $messageParts += "$($testResult.ArtifactsMissing.Count) artifacts missing" + } + if ($testResult.Errors.Count -gt 0) { + $messageParts += "$($testResult.Errors.Count) errors" + } + } + + $consolidatedMessage = $messageParts -join ", " + + $result = @{ + BuildSuccess = $buildResult.Success + TestSuccess = $testResult.Success + OverallSuccess = $overallSuccess + BuildExitCode = $buildResult.ExitCode + BuildMessage = $buildResult.Message + TotalArtifacts = $testResult.TotalArtifacts + ExpectedArtifacts = $testResult.ExpectedArtifacts + ArtifactsMissing = $testResult.ArtifactsMissing + Errors = $testResult.Errors + Warnings = $testResult.Warnings + LogFile = $buildResult.LogFile + BuildPath = $buildResult.BuildPath + Message = $consolidatedMessage + } + + return $result + +} catch { + Write-Host "`n========================================" -ForegroundColor Red + Write-Host "ERROR" -ForegroundColor Red + Write-Host "========================================" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + Write-Host $_.ScriptStackTrace -ForegroundColor Gray + + $result = @{ + BuildSuccess = $false + TestSuccess = $false + OverallSuccess = $false + BuildExitCode = -1 + BuildMessage = "Tool error: $($_.Exception.Message)" + TotalArtifacts = 0 + ExpectedArtifacts = 14 + ArtifactsMissing = @() + Errors = @() + Warnings = @() + LogFile = "" + BuildPath = "" + Message = "Build & test tool error: $($_.Exception.Message)" + } + + return $result +} diff --git a/.github/tools/Get-CommitGroups.ps1 b/.github/tools/Get-CommitGroups.ps1 new file mode 100644 index 000000000000..be7677800325 --- /dev/null +++ b/.github/tools/Get-CommitGroups.ps1 @@ -0,0 +1,402 @@ +<# +.SYNOPSIS + Groups upstream OpenSSH commits into batches based on CI success status or CI presence. + +.DESCRIPTION + This script fetches commits from the openssh/openssh-portable repository on GitHub + and groups them into batches (chunks) based on CI test results. By default, each chunk + ends with a commit that has all CI tests passing (success or skipped). Alternatively, + when using -GroupByCIPresence, each chunk ends with any commit that has CI runs, + regardless of success or failure. + + The script is designed to help with incremental merging of upstream commits by + identifying safe stopping points where all tests pass (or where CI exists). + + When the total number of commits exceeds 250 (GitHub API limit), the script + automatically adjusts to fetch the first 250 commits in chronological order. + +.PARAMETER GitHubTag + The GitHub tag to start from (e.g., "V_10_0_P2"). Cannot be used with -StartCommit. + The script will find commits after this tag up to HEAD. + +.PARAMETER StartCommit + The commit SHA to start from (e.g., "6fb728df50c1afd338cb0223a84ce24579577eff"). + Cannot be used with -GitHubTag. The script will find commits after this commit up to HEAD. + This is typically used when continuing from a previously merged commit. + +.PARAMETER FirstChunkOnly + When specified, the script stops after finding the first chunk with a successful CI commit + (or first chunk with any CI when using -GroupByCIPresence). + This is useful for incremental processing where you want to merge one batch at a time. + +.PARAMETER GroupByCIPresence + When specified, groups commits by CI presence (any CI run exists) rather than CI success. + Each chunk ends with a commit that has any CI runs, regardless of pass/fail status. + Default behavior (without this flag) groups by CI success only. + +.OUTPUTS + Returns an array of chunk objects, each containing: + - ChunkNumber: Sequential number of the chunk + - StartIndex/EndIndex: Array indices for the chunk range + - StartCommit/EndCommit: Short SHA (7 chars) of first and last commits + - StartCommitFull/EndCommitFull: Full SHA of first and last commits + - CommitCount: Number of commits in the chunk + - StartMessage/EndMessage: Commit messages for first and last commits + +.EXAMPLE + .\Get-CommitGroups.ps1 -GitHubTag "V_10_0_P2" -FirstChunkOnly + + Finds the first batch of commits after the V_10_0_P2 tag that ends with passing CI. + +.EXAMPLE + .\Get-CommitGroups.ps1 -StartCommit "6fb728df50c1afd338cb0223a84ce24579577eff" -FirstChunkOnly + + Finds the first batch of commits after the specified commit that ends with passing CI. + Useful for continuing from where a previous merge left off. + +.EXAMPLE + .\Get-CommitGroups.ps1 -StartCommit "6fb728df50c1afd338cb0223a84ce24579577eff" + + Finds all batches of commits after the specified commit, grouping by CI success. + Each batch ends with a commit that has passing CI tests. + +.EXAMPLE + .\Get-CommitGroups.ps1 -GitHubTag "V_10_0_P2" -GroupByCIPresence -FirstChunkOnly + + Finds the first batch of commits after the V_10_0_P2 tag that ends with any commit + that has CI runs (regardless of success or failure). + +.NOTES + - Requires internet access to query GitHub API + - Set GITHUB_TOKEN environment variable for authenticated API access (5,000 requests/hour) + - Without token: rate limited with 200ms delay between commits (60 requests/hour) + - Maximum 250 commits per API call (automatically handled) + - CI status is checked via GitHub check-runs API with pagination support + - Default mode: Commits with "success" or "skipped" CI conclusions are considered passing + - GroupByCIPresence mode: Any commit with CI runs (regardless of result) ends a chunk + +.LINK + https://github.com/openssh/openssh-portable +#> + +param( + [Parameter(Mandatory=$false)] + [string]$GitHubTag, + + [Parameter(Mandatory=$false)] + [string]$StartCommit, + + [Parameter(Mandatory=$false)] + [switch]$FirstChunkOnly, + + [Parameter(Mandatory=$false)] + [switch]$GroupByCIPresence +) + +# Validate parameters +if (-not $GitHubTag -and -not $StartCommit) { + Write-Error "Either -GitHubTag or -StartCommit must be provided" + exit 1 +} + +if ($GitHubTag -and $StartCommit) { + Write-Error "Cannot specify both -GitHubTag and -StartCommit. Please provide only one." + exit 1 +} + +# Configuration +$repo = "openssh/openssh-portable" +$apiBase = "https://api.github.com/repos/$repo" + +# Check for GitHub token for authenticated API access +$script:githubToken = $env:GITHUB_TOKEN +if ($script:githubToken) { + Write-Host "Using authenticated GitHub API (higher rate limits)" -ForegroundColor Green +} else { + Write-Host "Using unauthenticated GitHub API (rate limited - consider setting GITHUB_TOKEN)" -ForegroundColor Yellow +} + +if ($GitHubTag) { + Write-Host "Fetching commits starting from tag: $GitHubTag" -ForegroundColor Cyan +} else { + Write-Host "Fetching commits starting from commit: $StartCommit" -ForegroundColor Cyan +} + +# Function to get GitHub API headers with optional authentication +function Get-GitHubHeaders { + $headers = @{ + "User-Agent" = "PowerShell" + "Accept" = "application/vnd.github+json" + } + + if ($script:githubToken) { + $headers["Authorization"] = "Bearer $script:githubToken" + } + + return $headers +} + +# Get the starting commit SHA +try { + if ($GitHubTag) { + $tagInfo = Invoke-RestMethod -Uri "$apiBase/git/refs/tags/$GitHubTag" -Headers (Get-GitHubHeaders) + $startCommitSha = $tagInfo.object.sha + + # If it's an annotated tag, we need to get the actual commit + if ($tagInfo.object.type -eq "tag") { + $tagObject = Invoke-RestMethod -Uri $tagInfo.object.url -Headers (Get-GitHubHeaders) + $startCommitSha = $tagObject.object.sha + } + + Write-Host "Tag $GitHubTag points to commit: $startCommitSha" -ForegroundColor Green + } else { + # Validate the commit exists + $commitInfo = Invoke-RestMethod -Uri "$apiBase/commits/$StartCommit" -Headers (Get-GitHubHeaders) + $startCommitSha = $commitInfo.sha + Write-Host "Starting from commit: $startCommitSha" -ForegroundColor Green + } +} catch { + Write-Error "Failed to retrieve starting commit information: $_" + exit 1 +} + +# Function to check CI status for a commit +function Get-CommitCIStatus { + param( + [string]$sha, + [bool]$checkPresenceOnly = $false + ) + + try { + $allCheckRuns = @() + $page = 1 + $perPage = 100 + + # Fetch all pages of check runs + do { + $checkRunsUrl = "$apiBase/commits/$sha/check-runs?per_page=$perPage&page=$page" + $response = Invoke-RestMethod -Uri $checkRunsUrl -Headers (Get-GitHubHeaders) + + $allCheckRuns += $response.check_runs + $page++ + + } while ($response.check_runs.Count -eq $perPage) + + # Check if there are any check runs + if ($allCheckRuns.Count -eq 0) { + return "no_ci" + } + + # If only checking for presence, return has_ci + if ($checkPresenceOnly) { + return "has_ci" + } + + # Check if all check runs are successful or skipped + $allSuccessful = $true + foreach ($checkRun in $allCheckRuns) { + # Accept "success" or "skipped" as valid conclusions + if ($checkRun.conclusion -ne "success" -and $checkRun.conclusion -ne "skipped") { + $allSuccessful = $false + break + } + } + + if ($allSuccessful) { + return "success" + } else { + return "failure" + } + } catch { + Write-Warning "Could not get CI status for commit $sha : $_" + return "unknown" + } +} + +# Function to get commit details +function Get-CommitDetails { + param([string]$sha) + + try { + $commitUrl = "$apiBase/commits/$sha" + $commit = Invoke-RestMethod -Uri $commitUrl -Headers (Get-GitHubHeaders) + return @{ + Sha = $commit.sha.Substring(0, 7) + FullSha = $commit.sha + Message = $commit.commit.message.Split("`n")[0] + Author = $commit.commit.author.name + Date = $commit.commit.author.date + } + } catch { + Write-Warning "Could not get commit details for $sha" + return $null + } +} + +# Fetch commits starting from the tag using compare API +Write-Host "`nFetching commits from the repository..." -ForegroundColor Cyan + +try { + # Get commits after the starting commit (excluding the start commit itself) + $allCommits = @() + $page = 1 + $perPage = 250 # Compare API returns max 250 commits per page + + Write-Host "Fetching commits from $startCommitSha...HEAD" -ForegroundColor Gray + + # The Compare API doesn't support pagination, so we need to use commits API instead + # to get commits in the correct range with proper pagination + $compareUrl = "$apiBase/compare/${startCommitSha}...HEAD" + $comparison = Invoke-RestMethod -Uri $compareUrl -Headers (Get-GitHubHeaders) + + # The Compare API returns commits - need to verify order + # According to GitHub API docs, commits are in chronological order (oldest first) + $allCommits = @($comparison.commits) + + # If we hit the 250 commit limit, we need to get the actual first 250 commits + # by using the oldest commit from this batch as the end point + if ($comparison.total_commits -gt 250) { + Write-Host "Warning: Total commits ($($comparison.total_commits)) exceeds API limit (250)." -ForegroundColor Yellow + Write-Host "Fetching first 250 commits by using oldest commit as endpoint..." -ForegroundColor Cyan + + # Get the oldest commit SHA from the initial batch (first in chronological order) + $oldestCommitSha = $allCommits[0].sha + + # Now compare from start to this oldest commit to get the actual first 250 + $limitedCompareUrl = "$apiBase/compare/${startCommitSha}...${oldestCommitSha}" + Write-Host "Comparing $startCommitSha...$oldestCommitSha" -ForegroundColor Gray + $limitedComparison = Invoke-RestMethod -Uri $limitedCompareUrl -Headers (Get-GitHubHeaders) + + $allCommits = @($limitedComparison.commits) + Write-Host "Fetched $($allCommits.Count) commits in the corrected range" -ForegroundColor Green + } + + $startRef = if ($GitHubTag) { "tag $GitHubTag" } else { "commit $StartCommit" } + Write-Host "Found $($allCommits.Count) commits from $startRef" -ForegroundColor Green +} catch { + Write-Error "Failed to fetch commits: $_" + exit 1 +} + +# Process commits and check CI status +$statusCheckMessage = if ($GroupByCIPresence) { "Checking CI presence for each commit..." } else { "Checking CI status for each commit..." } +Write-Host "\n$statusCheckMessage" -ForegroundColor Cyan + +$commitsWithStatus = @() +$chunks = @() +$commitCount = 0 +$chunkStart = 0 + +foreach ($commit in $allCommits) { + $commitCount++ + Write-Host "Processing commit $commitCount of $($allCommits.Count): $($commit.sha.Substring(0,7))" -ForegroundColor Gray + + $status = Get-CommitCIStatus -sha $commit.sha -checkPresenceOnly $GroupByCIPresence + $details = Get-CommitDetails -sha $commit.sha + + if ($details) { + $commitsWithStatus += [PSCustomObject]@{ + Index = $commitCount - 1 + Sha = $details.Sha + FullSha = $details.FullSha + Message = $details.Message + Author = $details.Author + Date = $details.Date + CIStatus = $status + } + + # Check if this commit completes a chunk (based on grouping mode) + $targetStatus = if ($GroupByCIPresence) { "has_ci" } else { "success" } + if ($status -eq $targetStatus) { + $chunkEnd = $commitsWithStatus.Count - 1 + + $chunks += [PSCustomObject]@{ + ChunkNumber = $chunks.Count + 1 + StartIndex = $chunkStart + EndIndex = $chunkEnd + StartCommit = $commitsWithStatus[$chunkStart].Sha + EndCommit = $commitsWithStatus[$chunkEnd].Sha + StartCommitFull = $commitsWithStatus[$chunkStart].FullSha + EndCommitFull = $commitsWithStatus[$chunkEnd].FullSha + CommitCount = $chunkEnd - $chunkStart + 1 + StartMessage = $commitsWithStatus[$chunkStart].Message + EndMessage = $commitsWithStatus[$chunkEnd].Message + } + + $chunkStart = $commitsWithStatus.Count + + # If FirstChunkOnly is specified, stop after finding the first chunk + if ($FirstChunkOnly) { + $chunkFoundMessage = if ($GroupByCIPresence) { "Found first chunk with CI, stopping..." } else { "Found first successful chunk, stopping..." } + Write-Host $chunkFoundMessage -ForegroundColor Green + break + } + } + } + + # Rate limiting - only needed for unauthenticated requests + if (-not $script:githubToken) { + Start-Sleep -Milliseconds 200 + } +} + +# Handle remaining commits that don't end with success (only if not FirstChunkOnly or no chunk found) +if (-not $FirstChunkOnly -and $chunkStart -lt $commitsWithStatus.Count) { + $chunkEnd = $commitsWithStatus.Count - 1 + $chunks += [PSCustomObject]@{ + ChunkNumber = $chunks.Count + 1 + StartIndex = $chunkStart + EndIndex = $chunkEnd + StartCommit = $commitsWithStatus[$chunkStart].Sha + EndCommit = $commitsWithStatus[$chunkEnd].Sha + StartCommitFull = $commitsWithStatus[$chunkStart].FullSha + EndCommitFull = $commitsWithStatus[$chunkEnd].FullSha + CommitCount = $chunkEnd - $chunkStart + 1 + StartMessage = $commitsWithStatus[$chunkStart].Message + EndMessage = $commitsWithStatus[$chunkEnd].Message + } +} + +Write-Host "`nGrouping complete." -ForegroundColor Cyan + +# Display results +$groupingMode = if ($GroupByCIPresence) { "CI Presence" } else { "CI Success" } +Write-Host "\n========================================" -ForegroundColor Cyan +Write-Host "COMMIT CHUNKS (Grouped by $groupingMode)" -ForegroundColor Cyan +Write-Host "========================================`n" -ForegroundColor Cyan + +foreach ($chunk in $chunks) { + Write-Host "Chunk $($chunk.ChunkNumber): $($chunk.CommitCount) commits" -ForegroundColor Yellow + Write-Host " Start: $($chunk.StartCommit) - $($chunk.StartMessage)" -ForegroundColor White + Write-Host " End: $($chunk.EndCommit) - $($chunk.EndMessage)" -ForegroundColor Green + Write-Host " Commit Pair: ($($chunk.StartCommitFull), $($chunk.EndCommitFull))" -ForegroundColor Magenta + Write-Host "" +} + +Write-Host "`nTotal Chunks: $($chunks.Count)" -ForegroundColor Cyan +Write-Host "Total Commits: $($commitsWithStatus.Count)" -ForegroundColor Cyan + +# Output detailed commit list +Write-Host "`n========================================" -ForegroundColor Cyan +Write-Host "DETAILED COMMIT LIST" -ForegroundColor Cyan +Write-Host "========================================`n" -ForegroundColor Cyan + +# Only show commits that are part of returned chunks +$maxIndex = if ($chunks.Count -gt 0) { $chunks[-1].EndIndex } else { -1 } +foreach ($commit in $commitsWithStatus) { + if ($commit.Index -le $maxIndex) { + $statusColor = switch ($commit.CIStatus) { + "success" { "Green" } + "has_ci" { "Green" } + "failure" { "Red" } + "no_ci" { "Yellow" } + "pending" { "Yellow" } + default { "Gray" } + } + + Write-Host "$($commit.Sha) | $($commit.CIStatus.PadRight(10)) | $($commit.Message.Substring(0, [Math]::Min(60, $commit.Message.Length)))" -ForegroundColor $statusColor + } +} + +# Return the chunks for potential further processing +return $chunks diff --git a/.github/tools/Start-OpenSSHBuild.ps1 b/.github/tools/Start-OpenSSHBuild.ps1 new file mode 100644 index 000000000000..c175b29c6394 --- /dev/null +++ b/.github/tools/Start-OpenSSHBuild.ps1 @@ -0,0 +1,195 @@ +<# +.SYNOPSIS + MCP tool to build OpenSSH on Windows using Visual Studio. + +.DESCRIPTION + This script wraps the Start-OpenSSHBuild function from OpenSSHBuildHelper.psm1 + to provide a standardized MCP interface for building OpenSSH. It supports + incremental and clean builds, multiple architectures, and various build configurations. + + The tool invokes MSBuild on the Win32-OpenSSH.sln solution and captures + all build output to a log file. + +.PARAMETER Configuration + Build configuration type. Valid values: 'Debug', 'Release' + Default: 'Release' + +.PARAMETER Architecture + Target architecture for the build. Valid values: 'x64', 'x86', 'ARM', 'ARM64' + Default: 'x64' + +.PARAMETER Clean + When specified, performs a clean build by deleting existing build artifacts first. + Default: false (incremental build) + +.PARAMETER NoOpenSSL + Build without OpenSSL support. + Default: false + +.PARAMETER OneCore + Build for Windows OneCore API subset. + Default: false + +.OUTPUTS + Returns a hashtable with: + - Success: Boolean indicating build success + - ExitCode: MSBuild exit code + - LogFile: Path to build log file + - BuildPath: Path to build output directory + - Message: Status message + +.EXAMPLE + .\Start-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 + + Performs an incremental release build for x64 architecture. + +.EXAMPLE + .\Start-OpenSSHBuild.ps1 -Configuration Debug -Architecture x64 -Clean + + Performs a clean debug build for x64 architecture. + +.EXAMPLE + .\Start-OpenSSHBuild.ps1 -Architecture ARM64 -OneCore + + Performs an incremental OneCore release build for ARM64. + +.NOTES + - Requires Visual Studio 2019 or later with C++ tools + - Requires Windows SDK 10.0.17763.0 or later + - Build artifacts output to: contrib\win32\openssh\{Architecture}\{Configuration}\ + - Build log written to: OpenSSH{Configuration}{Architecture}.log +#> + +param( + [Parameter(Mandatory=$false)] + [ValidateSet('Debug', 'Release')] + [string]$Configuration = 'Release', + + [Parameter(Mandatory=$false)] + [ValidateSet('x64', 'x86', 'ARM', 'ARM64')] + [string]$Architecture = 'x64', + + [Parameter(Mandatory=$false)] + [switch]$Clean, + + [Parameter(Mandatory=$false)] + [switch]$NoOpenSSL, + + [Parameter(Mandatory=$false)] + [switch]$OneCore +) + +# Determine repository root (go up from .github\tools to repo root) +$scriptRoot = $PSScriptRoot +$repoRoot = Split-Path -Parent (Split-Path -Parent $scriptRoot) + +# Navigate to repository root +Push-Location $repoRoot + +try { + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "OpenSSH Build Tool (MCP)" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "Configuration: $Configuration" -ForegroundColor White + Write-Host "Architecture: $Architecture" -ForegroundColor White + Write-Host "Clean Build: $Clean" -ForegroundColor White + Write-Host "No OpenSSL: $NoOpenSSL" -ForegroundColor White + Write-Host "OneCore: $OneCore" -ForegroundColor White + Write-Host "========================================`n" -ForegroundColor Cyan + + # Import OpenSSH build helper module + $buildHelperPath = Join-Path $repoRoot "contrib\win32\openssh\OpenSSHBuildHelper.psm1" + if (-not (Test-Path $buildHelperPath)) { + throw "OpenSSHBuildHelper.psm1 not found at: $buildHelperPath" + } + + Import-Module $buildHelperPath -Force -ErrorAction Stop + Write-Host "✓ Loaded OpenSSHBuildHelper module" -ForegroundColor Green + + # Define build output path + $buildPath = Join-Path $repoRoot "contrib\win32\openssh\$Architecture\$Configuration" + + # Perform clean if requested + if ($Clean -and (Test-Path $buildPath)) { + Write-Host "`nCleaning previous build artifacts..." -ForegroundColor Yellow + Remove-Item $buildPath -Recurse -Force -ErrorAction Stop + Write-Host "✓ Cleaned: $buildPath" -ForegroundColor Green + } + + # Build log file path + $logFile = Join-Path $repoRoot "OpenSSH$Configuration$Architecture.log" + Write-Host "`nBuild log: $logFile" -ForegroundColor Gray + + # Prepare parameters for Start-OpenSSHBuild + $buildParams = @{ + Configuration = $Configuration + NativeHostArch = $Architecture + } + + if ($NoOpenSSL) { + $buildParams['NoOpenSSL'] = $true + } + + if ($OneCore) { + $buildParams['OneCore'] = $true + } + + # Execute build + Write-Host "`nStarting build..." -ForegroundColor Cyan + Write-Host "Command: Start-OpenSSHBuild -Configuration $Configuration -NativeHostArch $Architecture$(if($NoOpenSSL){' -NoOpenSSL'})$(if($OneCore){' -OneCore'})" -ForegroundColor Gray + + $buildResult = Start-OpenSSHBuild @buildParams + + # Check build result + if ($buildResult -eq 0) { + Write-Host "`n========================================" -ForegroundColor Green + Write-Host "BUILD SUCCEEDED" -ForegroundColor Green + Write-Host "========================================" -ForegroundColor Green + Write-Host "Build artifacts: $buildPath" -ForegroundColor White + + $result = @{ + Success = $true + ExitCode = 0 + LogFile = $logFile + BuildPath = $buildPath + Message = "Build completed successfully" + } + } else { + Write-Host "`n========================================" -ForegroundColor Red + Write-Host "BUILD FAILED" -ForegroundColor Red + Write-Host "========================================" -ForegroundColor Red + Write-Host "Exit code: $buildResult" -ForegroundColor Red + Write-Host "Check log file: $logFile" -ForegroundColor Yellow + + $result = @{ + Success = $false + ExitCode = $buildResult + LogFile = $logFile + BuildPath = $buildPath + Message = "Build failed with exit code $buildResult. Check log file for details." + } + } + + # Output result as JSON for MCP consumption + return $result + +} catch { + Write-Host "`n========================================" -ForegroundColor Red + Write-Host "ERROR" -ForegroundColor Red + Write-Host "========================================" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + Write-Host $_.ScriptStackTrace -ForegroundColor Gray + + $result = @{ + Success = $false + ExitCode = -1 + LogFile = $logFile + BuildPath = $buildPath + Message = "Build tool error: $($_.Exception.Message)" + } + + return $result + +} finally { + Pop-Location +} diff --git a/.github/tools/Test-OpenSSHBuild.ps1 b/.github/tools/Test-OpenSSHBuild.ps1 new file mode 100644 index 000000000000..25aa95f4571c --- /dev/null +++ b/.github/tools/Test-OpenSSHBuild.ps1 @@ -0,0 +1,262 @@ +<# +.SYNOPSIS + MCP tool to test OpenSSH build artifacts and parse build errors. + +.DESCRIPTION + This script tests that all expected OpenSSH executables were built successfully + and parses the build log file for any compilation or linker errors using regex patterns. + + Expected artifacts (14 executables): + - ssh.exe, sshd.exe, sshd-auth.exe, sshd-session.exe + - ssh-agent.exe, ssh-add.exe, ssh-keygen.exe, ssh-keyscan.exe + - scp.exe, sftp.exe, sftp-server.exe + - ssh-pkcs11-helper.exe, ssh-shellhost.exe, ssh-sk-helper.exe + +.PARAMETER Configuration + Build configuration type that was used. Valid values: 'Debug', 'Release' + Default: 'Release' + +.PARAMETER Architecture + Target architecture that was built. Valid values: 'x64', 'x86', 'ARM', 'ARM64' + Default: 'x64' + +.PARAMETER LogFile + Optional path to the build log file. If not specified, uses default pattern: + OpenSSH{Configuration}{Architecture}.log in repository root. + +.OUTPUTS + Returns a hashtable with: + - Success: Boolean indicating if all artifacts present and no errors + - ArtifactsFound: Array of executables that exist + - ArtifactsMissing: Array of expected executables that are missing + - TotalArtifacts: Count of artifacts found + - ExpectedArtifacts: Count of artifacts expected (14) + - Errors: Array of parsed error objects with file, line, code, message + - Warnings: Array of parsed warning objects + - LogFile: Path to log file analyzed + - Message: Summary message + +.EXAMPLE + .\Test-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 + + Tests release build artifacts for x64 architecture. + +.EXAMPLE + .\Test-OpenSSHBuild.ps1 -Configuration Debug -Architecture x86 -LogFile "C:\build\custom.log" + + Tests debug build artifacts for x86 using a custom log file location. + +.NOTES + - Expected build artifact location: contrib\win32\openssh\{Architecture}\{Configuration}\ + - Error parsing regex: ^(?.*?)\((?\d+)[,)].*?error (?(C|LNK)\d+): (?.*)$ + - Warning parsing regex: ^(?.*?)\((?\d+)[,)].*?warning (?(C|LNK)\d+): (?.*)$ +#> + +param( + [Parameter(Mandatory=$false)] + [ValidateSet('Debug', 'Release')] + [string]$Configuration = 'Release', + + [Parameter(Mandatory=$false)] + [ValidateSet('x64', 'x86', 'ARM', 'ARM64')] + [string]$Architecture = 'x64', + + [Parameter(Mandatory=$false)] + [string]$LogFile +) + +# Determine repository root (go up from .github\tools to repo root) +$scriptRoot = $PSScriptRoot +$repoRoot = Split-Path -Parent (Split-Path -Parent $scriptRoot) + +# Define expected artifacts (14 executables) +$expectedArtifacts = @( + "ssh.exe", + "sshd.exe", + "sshd-auth.exe", + "sshd-session.exe", + "ssh-agent.exe", + "ssh-add.exe", + "ssh-keygen.exe", + "ssh-keyscan.exe", + "scp.exe", + "sftp.exe", + "sftp-server.exe", + "ssh-pkcs11-helper.exe", + "ssh-shellhost.exe", + "ssh-sk-helper.exe" +) + +try { + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "OpenSSH Build Test Tool (MCP)" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "Configuration: $Configuration" -ForegroundColor White + Write-Host "Architecture: $Architecture" -ForegroundColor White + Write-Host "========================================`n" -ForegroundColor Cyan + + # Define build output path + $buildPath = Join-Path $repoRoot "contrib\win32\openssh\$Architecture\$Configuration" + + if (-not (Test-Path $buildPath)) { + Write-Host "Build path does not exist: $buildPath" -ForegroundColor Red + + $result = @{ + Success = $false + ArtifactsFound = @() + ArtifactsMissing = $expectedArtifacts + TotalArtifacts = 0 + ExpectedArtifacts = $expectedArtifacts.Count + Errors = @() + Warnings = @() + LogFile = $LogFile + Message = "Build path does not exist: $buildPath" + } + + return $result + } + + Write-Host "Build path: $buildPath" -ForegroundColor Gray + + # Check for artifacts + Write-Host "`nChecking for expected artifacts..." -ForegroundColor Cyan + + $artifactsFound = @() + $artifactsMissing = @() + + foreach ($artifact in $expectedArtifacts) { + $artifactPath = Join-Path $buildPath $artifact + if (Test-Path $artifactPath) { + $artifactsFound += $artifact + Write-Host " ✓ $artifact" -ForegroundColor Green + } else { + $artifactsMissing += $artifact + Write-Host " ✗ $artifact (MISSING)" -ForegroundColor Red + } + } + + Write-Host "`nArtifacts: $($artifactsFound.Count) of $($expectedArtifacts.Count) found" -ForegroundColor $(if ($artifactsMissing.Count -eq 0) { "Green" } else { "Yellow" }) + + # Parse build log if available + $errors = @() + $warnings = @() + + if (-not $LogFile) { + $LogFile = Join-Path $repoRoot "OpenSSH$Configuration$Architecture.log" + } + + if (Test-Path $LogFile) { + Write-Host "`nParsing build log: $LogFile" -ForegroundColor Cyan + + $logContent = Get-Content $LogFile -ErrorAction SilentlyContinue + + if ($logContent) { + # Error regex: file(line) : error CODE: message + # Example: c:\path\file.c(123): error C2065: 'identifier' : undeclared identifier + $errorRegex = '^(?.*?)\((?\d+)[,)].*?error (?(C|LNK)\d+): (?.*)$' + + # Warning regex: similar pattern for warnings + $warningRegex = '^(?.*?)\((?\d+)[,)].*?warning (?(C|LNK)\d+): (?.*)$' + + foreach ($line in $logContent) { + # Check for errors + if ($line -match $errorRegex) { + $errors += [PSCustomObject]@{ + File = $matches['file'] + Line = [int]$matches['line'] + Code = $matches['code'] + Message = $matches['message'] + RawLine = $line + } + } + # Check for warnings + elseif ($line -match $warningRegex) { + $warnings += [PSCustomObject]@{ + File = $matches['file'] + Line = [int]$matches['line'] + Code = $matches['code'] + Message = $matches['message'] + RawLine = $line + } + } + } + + if ($errors.Count -gt 0) { + Write-Host "`nFound $($errors.Count) error(s) in build log:" -ForegroundColor Red + foreach ($e in $errors) { + Write-Host " $($e.File)($($e.Line)): error $($e.Code): $($e.Message)" -ForegroundColor Red + } + } else { + Write-Host "`n✓ No errors found in build log" -ForegroundColor Green + } + + if ($warnings.Count -gt 0) { + Write-Host "`nFound $($warnings.Count) warning(s) in build log:" -ForegroundColor Yellow + foreach ($warning in $warnings | Select-Object -First 10) { + Write-Host " $($warning.File)($($warning.Line)): warning $($warning.Code): $($warning.Message)" -ForegroundColor Yellow + } + if ($warnings.Count -gt 10) { + Write-Host " ... and $($warnings.Count - 10) more warnings" -ForegroundColor Gray + } + } + } else { + Write-Host "Log file is empty or could not be read" -ForegroundColor Yellow + } + } else { + Write-Host "`nBuild log not found: $LogFile" -ForegroundColor Yellow + } + + # Determine overall success + $success = ($artifactsMissing.Count -eq 0) -and ($errors.Count -eq 0) + + # Build summary message + if ($success) { + $message = "All $($expectedArtifacts.Count) artifacts built successfully with no errors" + } elseif ($artifactsMissing.Count -gt 0 -and $errors.Count -gt 0) { + $message = "$($artifactsMissing.Count) artifacts missing and $($errors.Count) error(s) found" + } elseif ($artifactsMissing.Count -gt 0) { + $message = "$($artifactsMissing.Count) artifacts missing" + } else { + $message = "$($errors.Count) error(s) found in build log" + } + + Write-Host "`n========================================" -ForegroundColor $(if ($success) { "Green" } else { "Red" }) + Write-Host $(if ($success) { "TEST PASSED" } else { "TEST FAILED" }) -ForegroundColor $(if ($success) { "Green" } else { "Red" }) + Write-Host "========================================" -ForegroundColor $(if ($success) { "Green" } else { "Red" }) + Write-Host $message -ForegroundColor White + + $result = @{ + Success = $success + ArtifactsFound = $artifactsFound + ArtifactsMissing = $artifactsMissing + TotalArtifacts = $artifactsFound.Count + ExpectedArtifacts = $expectedArtifacts.Count + Errors = $errors + Warnings = $warnings + LogFile = $LogFile + Message = $message + } + + return $result + +} catch { + Write-Host "`n========================================" -ForegroundColor Red + Write-Host "ERROR" -ForegroundColor Red + Write-Host "========================================" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + Write-Host $_.ScriptStackTrace -ForegroundColor Gray + + $result = @{ + Success = $false + ArtifactsFound = @() + ArtifactsMissing = $expectedArtifacts + TotalArtifacts = 0 + ExpectedArtifacts = $expectedArtifacts.Count + Errors = @() + Warnings = @() + LogFile = $LogFile + Message = "Test tool error: $($_.Exception.Message)" + } + + return $result +} diff --git a/.github/tools/Test-OpenSSHFunctionality.ps1 b/.github/tools/Test-OpenSSHFunctionality.ps1 new file mode 100644 index 000000000000..9e1b0f7c8907 --- /dev/null +++ b/.github/tools/Test-OpenSSHFunctionality.ps1 @@ -0,0 +1,447 @@ +<# +.SYNOPSIS + Tests OpenSSH functionality by installing the SSH service, creating a test user, + and attempting a password-authenticated SSH connection. + +.DESCRIPTION + This script performs end-to-end functional testing of OpenSSH on Windows by: + 1. Verifying Administrator privileges + 2. Creating a temporary local user with a random password + 3. Installing the SSH service using install-sshd.ps1 + 4. Starting the SSH service + 5. Configuring Windows Firewall (optional) + 6. Testing SSH connection with password authentication + 7. Cleaning up all resources (user, service, firewall rule) + + The test is successful if an SSH connection can execute "echo hello world" successfully. + +.PARAMETER Configuration + Build configuration type. Valid values: 'Debug', 'Release' + Default: 'Release' + +.PARAMETER Architecture + Target architecture. Valid values: 'x64', 'x86', 'ARM', 'ARM64' + Default: 'x64' + +.PARAMETER SkipFirewall + Skip Windows Firewall configuration. Use this if firewall rules already exist + or if testing on a system without firewall enabled. + +.PARAMETER NoCleanup + Skip cleanup of created resources (test user, firewall rule, temp files). + Useful when debugging failures. + +.EXAMPLE + .\Test-OpenSSHFunctionality.ps1 + Tests using default Release x64 build with firewall configuration + +.EXAMPLE + .\Test-OpenSSHFunctionality.ps1 -Configuration Debug -Architecture x64 + Tests using Debug x64 build + +.EXAMPLE + .\Test-OpenSSHFunctionality.ps1 -SkipFirewall + Tests without modifying firewall rules + +.NOTES + Requires Administrator privileges. + Creates temporary user with prefix "openssh_test_" which is removed after testing. +#> + +[CmdletBinding()] +param( + [Parameter()] + [ValidateSet('Debug', 'Release')] + [string]$Configuration = 'Release', + + [Parameter()] + [ValidateSet('x64', 'x86', 'ARM', 'ARM64')] + [string]$Architecture = 'x64', + + [Parameter()] + [switch]$SkipFirewall, + + [Parameter()] + [switch]$NoCleanup +) + +# Helper function to generate a random password +function New-RandomPassword { + [CmdletBinding()] + param( + [Parameter()] + [int]$Length = 20 + ) + + # Character sets for password complexity + $lowercase = 'abcdefghijklmnopqrstuvwxyz' + $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + $numbers = '0123456789' + $special = '!@#$%^&*()_+-=[]{}|;:,.<>?' + + # Ensure at least one character from each set + $password = @( + $lowercase[(Get-Random -Maximum $lowercase.Length)] + $uppercase[(Get-Random -Maximum $uppercase.Length)] + $numbers[(Get-Random -Maximum $numbers.Length)] + $special[(Get-Random -Maximum $special.Length)] + ) + + # Fill the rest with random characters from all sets + $allChars = $lowercase + $uppercase + $numbers + $special + for ($i = $password.Count; $i -lt $Length; $i++) { + $password += $allChars[(Get-Random -Maximum $allChars.Length)] + } + + # Shuffle the password + $shuffled = $password | Sort-Object {Get-Random} + + return -join $shuffled +} + +# Initialize result object +$result = [PSCustomObject]@{ + Success = $false + ServiceInstalled = $false + ServiceStarted = $false + ConnectionSuccessful = $false + CommandOutput = $null + TestUser = $null + Errors = @() + Message = "" +} + +# Variables for cleanup tracking +$testUser = $null +$serviceWasInstalled = $false +$firewallRuleCreated = $false + +try { + Write-Host "=== OpenSSH Functionality Test ===" -ForegroundColor Cyan + Write-Host "Configuration: $Configuration" -ForegroundColor Gray + Write-Host "Architecture: $Architecture" -ForegroundColor Gray + Write-Host "" + + # Step 1: Verify Administrator privileges + Write-Host "[1/6] Checking Administrator privileges..." -ForegroundColor Yellow + $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") + + if (-not $isAdmin) { + $result.Errors += "Administrator privileges required" + $result.Message = "FAILED: This script must be run as Administrator for service installation and user management." + Write-Host "✗ Administrator privileges required" -ForegroundColor Red + Write-Host " Please restart PowerShell or VS Code as Administrator" -ForegroundColor Yellow + return $result + } + Write-Host "✓ Running with Administrator privileges" -ForegroundColor Green + + # Step 2: Locate build artifacts and scripts + $scriptRoot = Split-Path -Parent $PSCommandPath + $repoRoot = Split-Path -Parent (Split-Path -Parent $scriptRoot) + $buildPath = Join-Path $repoRoot "bin\$Architecture\$Configuration" + $askPassExe = Join-Path $repoRoot "regress\pesterTests\utilities\askpass_util\askpass_util.exe" + + # Verify build artifacts exist + $sshdExe = Join-Path $buildPath "sshd.exe" + $sshExe = Join-Path $buildPath "ssh.exe" + $installScript = Join-Path $buildPath "install-sshd.ps1" + + if (-not (Test-Path $sshdExe) -or -not (Test-Path $sshExe)) { + $result.Errors += "Build artifacts not found at $buildPath" + $result.Message = "FAILED: Build artifacts not found. Please build the project first." + Write-Host "✗ Build artifacts not found at $buildPath" -ForegroundColor Red + return $result + } + + if (-not (Test-Path $installScript)) { + $result.Errors += "install-sshd.ps1 not found at $buildPath" + $result.Message = "FAILED: install-sshd.ps1 script not found." + Write-Host "✗ install-sshd.ps1 not found at $buildPath" -ForegroundColor Red + return $result + } + + # Step 3: Create temporary test user + Write-Host "`n[2/6] Creating temporary test user..." -ForegroundColor Yellow + $testUsername = "openssh_test_" + (Get-Random -Minimum 1000 -Maximum 9999) + $testPassword = New-RandomPassword -Length 24 + $securePassword = ConvertTo-SecureString $testPassword -AsPlainText -Force + + try { + Import-Module Microsoft.PowerShell.LocalAccounts -UseWindowsPowerShell + New-LocalUser -Name $testUsername -Password $securePassword -Description "Temporary user for OpenSSH testing" -ErrorAction Stop | Out-Null + $testUser = $testUsername + $result.TestUser = $testUsername + $env:ASKPASS_PASSWORD = $testPassword + $env:SSH_ASKPASS_REQUIRE = "force" + if (-not (Test-Path $askPassExe)) { + throw "SSH_ASKPASS helper not found at '$askPassExe'" + } + $env:SSH_ASKPASS = $askPassExe + Write-Host "✓ Created test user: $testUsername" -ForegroundColor Green + } + catch { + $result.Errors += "Failed to create test user: $_" + $result.Message = "FAILED: Could not create temporary test user." + Write-Host "✗ Failed to create test user: $_" -ForegroundColor Red + return $result + } + + # Step 4: Install SSH service + Write-Host "`n[3/6] Installing SSH service..." -ForegroundColor Yellow + Push-Location $buildPath + try { + # Check if service already exists + $existingService = Get-Service sshd -ErrorAction SilentlyContinue + if ($existingService) { + Write-Host " SSH service already exists, uninstalling first..." -ForegroundColor Gray + $uninstallScript = Join-Path $buildPath "uninstall-sshd.ps1" + if (Test-Path $uninstallScript) { + & $uninstallScript 2>&1 | Out-Null + Start-Sleep -Seconds 2 + } + } + + # Install the service + & $installScript 2>&1 | Out-Null + $serviceWasInstalled = $true + + # Verify installation + $service = Get-Service sshd -ErrorAction SilentlyContinue + if ($service) { + $result.ServiceInstalled = $true + Write-Host "✓ SSH service installed successfully" -ForegroundColor Green + } + else { + throw "Service installation completed but service not found" + } + } + catch { + $result.Errors += "Failed to install SSH service: $_" + $result.Message = "FAILED: SSH service installation failed." + Write-Host "✗ Failed to install SSH service: $_" -ForegroundColor Red + return $result + } + finally { + Pop-Location + } + + # Step 5: Start SSH service + Write-Host "`n[4/6] Starting SSH service..." -ForegroundColor Yellow + try { + Start-Service sshd -ErrorAction Stop + Start-Sleep -Seconds 2 + + $service = Get-Service sshd + if ($service.Status -eq 'Running') { + $result.ServiceStarted = $true + Write-Host "✓ SSH service started successfully" -ForegroundColor Green + } + else { + throw "Service status is $($service.Status), expected Running" + } + } + catch { + $result.Errors += "Failed to start SSH service: $_" + $result.Message = "FAILED: Could not start SSH service." + Write-Host "✗ Failed to start SSH service: $_" -ForegroundColor Red + return $result + } + + # Step 6: Configure Windows Firewall (if not skipped) + if (-not $SkipFirewall) { + Write-Host "`n[5/6] Configuring Windows Firewall..." -ForegroundColor Yellow + try { + # Check if rule already exists + $existingRule = Get-NetFirewallRule -DisplayName "SSH Server (sshd) - Test" -ErrorAction SilentlyContinue + if ($existingRule) { + Remove-NetFirewallRule -DisplayName "SSH Server (sshd) - Test" -ErrorAction SilentlyContinue + } + + New-NetFirewallRule -DisplayName "SSH Server (sshd) - Test" -Direction Inbound -Port 22 -Protocol TCP -Action Allow -ErrorAction Stop | Out-Null + $firewallRuleCreated = $true + Write-Host "✓ Firewall rule created" -ForegroundColor Green + } + catch { + # Non-critical error, continue with test + Write-Host "⚠ Firewall configuration failed (non-critical): $_" -ForegroundColor Yellow + } + } + else { + Write-Host "`n[5/6] Skipping firewall configuration (as requested)" -ForegroundColor Gray + } + + # Step 7: Test SSH connection + Write-Host "`n[6/6] Testing SSH connection..." -ForegroundColor Yellow + + # Prepare connection test + $sshClientPath = Join-Path $buildPath "ssh.exe" + $hostname = "localhost" + $testCommand = "echo hello world" + + # Create temporary files for password input and output capture + $tempPasswordFile = [System.IO.Path]::GetTempFileName() + $tempOutputFile = [System.IO.Path]::GetTempFileName() + $tempErrorFile = [System.IO.Path]::GetTempFileName() + + try { + # Write password to temp file (for potential use, though we'll use environment variable) + Set-Content -Path $tempPasswordFile -Value $testPassword -NoNewline + + # Build SSH command with password authentication forced + # Note: Windows SSH doesn't support stdin password directly, so we rely on interactive prompt handling + # For automated testing, we'll use SSH with key-based auth disabled and capture output + + $processInfo = New-Object System.Diagnostics.ProcessStartInfo + $processInfo.FileName = $sshClientPath + $processInfo.Arguments = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL -o PubkeyAuthentication=no -o PasswordAuthentication=yes $testUsername@$hostname `"$testCommand`"" + $processInfo.RedirectStandardInput = $true + $processInfo.RedirectStandardOutput = $true + $processInfo.RedirectStandardError = $true + $processInfo.UseShellExecute = $false + $processInfo.CreateNoWindow = $true + + $process = New-Object System.Diagnostics.Process + $process.StartInfo = $processInfo + + Write-Host " Attempting SSH connection to $testUsername@$hostname..." -ForegroundColor Gray + + $process.Start() | Out-Null + + # Wait for completion (with timeout) + $completed = $process.WaitForExit(15000) # 15 second timeout + + if (-not $completed) { + $process.Kill() + throw "SSH connection timed out after 15 seconds" + } + + $stdout = $process.StandardOutput.ReadToEnd() + $stderr = $process.StandardError.ReadToEnd() + $exitCode = $process.ExitCode + + # Check result + if ($exitCode -eq 0 -and $stdout -match "hello world") { + $result.ConnectionSuccessful = $true + $result.CommandOutput = $stdout.Trim() + $result.Success = $true + $result.Message = "SUCCESS: SSH connection test passed. Command executed successfully." + Write-Host "✓ SSH connection successful" -ForegroundColor Green + Write-Host " Command output: $($stdout.Trim())" -ForegroundColor Gray + } + else { + $result.Errors += "SSH connection failed with exit code $exitCode" + if ($stderr) { + $result.Errors += "SSH error: $stderr" + } + $result.Message = "FAILED: SSH connection test failed." + Write-Host "✗ SSH connection failed (exit code: $exitCode)" -ForegroundColor Red + if ($stderr) { + Write-Host " Error: $stderr" -ForegroundColor Red + } + } + } + catch { + $result.Errors += "SSH connection test error: $_" + $result.Message = "FAILED: SSH connection test encountered an error." + Write-Host "✗ SSH connection test error: $_" -ForegroundColor Red + } + finally { + # Clean up temp files + if (Test-Path $tempPasswordFile) { Remove-Item $tempPasswordFile -Force -ErrorAction SilentlyContinue } + if (Test-Path $tempOutputFile) { Remove-Item $tempOutputFile -Force -ErrorAction SilentlyContinue } + if (Test-Path $tempErrorFile) { Remove-Item $tempErrorFile -Force -ErrorAction SilentlyContinue } + } +} +finally { + # Cleanup: Always attempt to clean up resources + Write-Host "`n=== Cleanup ===" -ForegroundColor Cyan + + if ($NoCleanup) { + Write-Host "⚠ NoCleanup specified - leaving resources in place for investigation." -ForegroundColor Yellow + if ($testUser) { + Write-Host " Test user: $testUser" -ForegroundColor Yellow + } + if ($serviceWasInstalled) { + Write-Host " SSH service may still be installed/running (sshd)." -ForegroundColor Yellow + } + if ($firewallRuleCreated) { + Write-Host " Firewall rule: SSH Server (sshd) - Test" -ForegroundColor Yellow + } + Write-Host ""; + } else { + # Stop and uninstall SSH service + if ($serviceWasInstalled) { + Write-Host "Stopping SSH service..." -ForegroundColor Gray + try { + Stop-Service sshd -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 1 + } + catch { + Write-Host "⚠ Warning: Failed to stop service: $_" -ForegroundColor Yellow + } + + Write-Host "Uninstalling SSH service..." -ForegroundColor Gray + $uninstallScript = Join-Path $buildPath "uninstall-sshd.ps1" + if (Test-Path $uninstallScript) { + Push-Location $buildPath + try { + & $uninstallScript 2>&1 | Out-Null + Write-Host "✓ SSH service uninstalled" -ForegroundColor Green + } + catch { + Write-Host "⚠ Warning: Failed to uninstall service: $_" -ForegroundColor Yellow + } + finally { + Pop-Location + } + } + } + + # Remove firewall rule + if ($firewallRuleCreated) { + Write-Host "Removing firewall rule..." -ForegroundColor Gray + try { + Remove-NetFirewallRule -DisplayName "SSH Server (sshd) - Test" -ErrorAction SilentlyContinue + Write-Host "✓ Firewall rule removed" -ForegroundColor Green + } + catch { + Write-Host "⚠ Warning: Failed to remove firewall rule: $_" -ForegroundColor Yellow + } + } + + # Remove test user + if ($testUser) { + Write-Host "Removing test user..." -ForegroundColor Gray + try { + Remove-LocalUser -Name $testUser -ErrorAction Stop + Write-Host "✓ Test user removed" -ForegroundColor Green + } + catch { + Write-Host "⚠ Warning: Failed to remove test user: $_" -ForegroundColor Yellow + Write-Host " You may need to manually remove user: $testUser" -ForegroundColor Yellow + } + } + + Write-Host "" + } +} + +# Output summary +Write-Host "=== Test Summary ===" -ForegroundColor Cyan +Write-Host "Status: $(if ($result.Success) { 'PASSED' } else { 'FAILED' })" -ForegroundColor $(if ($result.Success) { 'Green' } else { 'Red' }) +Write-Host "Service Installed: $($result.ServiceInstalled)" -ForegroundColor Gray +Write-Host "Service Started: $($result.ServiceStarted)" -ForegroundColor Gray +Write-Host "Connection Successful: $($result.ConnectionSuccessful)" -ForegroundColor Gray +if ($result.CommandOutput) { + Write-Host "Command Output: $($result.CommandOutput)" -ForegroundColor Gray +} +if ($result.Errors.Count -gt 0) { + Write-Host "Errors:" -ForegroundColor Red + foreach ($e in $result.Errors) { + Write-Host " - $e" -ForegroundColor Red + } +} +Write-Host "" + +# Return result object +return $result From 1053ed352101d114f969bce07efb9d8c1329a642 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 12:10:08 -0800 Subject: [PATCH 33/68] update instructions to branch off of current branch instead of latestw_all --- .github/agents/merge-upstream.agent.md | 2 +- .../merge/merge-details.instructions.md | 2 +- .../merge/merge-process-overview.instructions.md | 13 ++++--------- .github/instructions/merge/research.instructions.md | 2 +- .github/instructions/setup.instructions.md | 3 +-- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index a5565b25b883..240b1b956458 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -81,7 +81,7 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for 2. Confirm repository remotes are configured 3. Identify upstream version/tag to merge 4. Create merge branch: `merge-v-` -5. Perform baseline build on `latestw_all` branch +5. Perform baseline build from current branch 6. Use Get-CommitGroups with `-FirstChunkOnly -GroupByCIPresence` to get first commit batch **Success Criteria:** diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md index b2214858543c..5c6f20d909d4 100644 --- a/.github/instructions/merge/merge-details.instructions.md +++ b/.github/instructions/merge/merge-details.instructions.md @@ -93,7 +93,7 @@ FUNCTION resolve_conflict(file_path, conflict_content): git show # Compare files between branches -git diff upstream-pwsh/latestw_all upstream/ -- +git diff HEAD upstream/ -- ``` ## Conflict Resolution Strategies diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 34f4b413a6b5..fd8cce4b2620 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -47,24 +47,19 @@ The process consists of several interconnected phases: ## Merge Phase ### Initial Preparation -1. **Checkout base branch:** - ```pwsh - git checkout upstream-pwsh/latestw_all - ``` - -2. **Verify baseline build** +1. **Verify baseline build** **(📖 Detailed Instructions:** [Build Instructions](../build.instructions.md)): ```pwsh Import-Module .\contrib\win32\openssh\OpenSSHBuildHelper.psm1 -Force Start-OpenSSHBuild -Configuration Release -NativeHostArch x64 ``` -3. **Configure git:** +2. **Configure git:** ```pwsh git config core.editor true ``` -3. **Create merge branch:** +3. **Create merge branch from current branch:** ```pwsh git checkout -b merge-v- # Example: merge-v9.8-20241010 @@ -235,7 +230,7 @@ The process consists of several interconnected phases: ``` 15. **Create Pull Request:** - - Target: `PowerShell/openssh-portable:latestw_all` + - Target: `PowerShell/openssh-portable:` (typically the branch you started from, e.g., latestw_all) - Title: `Merge upstream OpenSSH ` - Include comprehensive description of changes and resolutions diff --git a/.github/instructions/merge/research.instructions.md b/.github/instructions/merge/research.instructions.md index f8b8c5032d5c..49b6b56a4226 100644 --- a/.github/instructions/merge/research.instructions.md +++ b/.github/instructions/merge/research.instructions.md @@ -63,7 +63,7 @@ git log --oneline upstream/ --since="" git show # Compare branches -git diff upstream-pwsh/latestw_all upstream/ +git diff HEAD upstream/ ``` ## Windows-Specific Knowledge Base diff --git a/.github/instructions/setup.instructions.md b/.github/instructions/setup.instructions.md index 3081ae3cfdd2..5ec3962af00f 100644 --- a/.github/instructions/setup.instructions.md +++ b/.github/instructions/setup.instructions.md @@ -42,7 +42,7 @@ git fetch --all ## Branch Strategy ### Understanding the Branch Structure -- **upstream-pwsh/latestw_all**: Main Windows-compatible branch (base for merges) +- **upstream-pwsh/latestw_all**: Main Windows-compatible branch - **upstream/master**: Latest upstream OpenSSH development - **upstream/V_X_Y_PZ**: Tagged releases (merge targets) @@ -74,7 +74,6 @@ Before proceeding to merge: - [ ] Repository cloned successfully - [ ] All three remotes configured (origin, upstream, upstream-pwsh) - [ ] Can fetch from all remotes without errors -- [ ] Can see upstream-pwsh/latestw_all branch - [ ] Can see upstream target version/branch - [ ] Working directory is clean (`git status` shows no uncommitted changes) From 2955382ceb21b7c65455cc373d5a312758a47414 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 12:17:50 -0800 Subject: [PATCH 34/68] remove direct references to scripts for mcp tools --- .github/agents/merge-upstream.agent.md | 21 +++++++----- .github/instructions/build.instructions.md | 32 ------------------- .../merge-process-overview.instructions.md | 1 - 3 files changed, 13 insertions(+), 41 deletions(-) diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index 240b1b956458..9014f07e45e5 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -24,23 +24,28 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for ### Key Tools Available 1. **Get-CommitGroups MCP Tool** - Groups commits by CI presence or success - - **Access**: Available via MCP server (preferred method) + - **Access**: Available via MCP server - **MCP Tool Name**: `mcp_pwsh-mcp-server_Get_CommitGroups` - - **Direct Script**: `.github/tools/Get-CommitGroups.ps1` (fallback) - **Parameters**: - `GitHubTag` (string, optional): GitHub tag to start from (e.g., "V_10_0_P2") - `StartCommit` (string, optional): Commit SHA to start from - `FirstChunkOnly` (boolean, optional): Stop after finding first chunk - `GroupByCIPresence` (boolean, optional): Group by CI presence instead of CI success - **Recommended Usage**: Always use `-FirstChunkOnly -GroupByCIPresence` for incremental merging - - **Usage via MCP**: Use the MCP tool function directly - it handles all GitHub API calls - - **Manual Usage**: `Get-Help .\Get-CommitGroups.ps1` for direct script execution + - **Usage**: Use the MCP tool function directly - it handles all GitHub API calls + - **If tool unavailable**: ERROR - This tool is required for the merge workflow -2. **OpenSSHBuildHelper Module** - Build automation - - Location: `contrib\win32\openssh\OpenSSHBuildHelper.psm1` - - Key cmdlet: `Start-OpenSSHBuild -Configuration Release -NativeHostArch x64` +2. **Build-OpenSSH MCP Tool** - Build automation and verification + - **Access**: Available via `.github/tools/Build-OpenSSH.ps1` + - **Usage**: `.github/tools/Build-OpenSSH.ps1 -Configuration Release -Architecture x64` + - **If tool unavailable**: ERROR - This tool is required for the merge workflow -3. **Git** - Version control operations +3. **Test-OpenSSHFunctionality MCP Tool** - Functional testing + - **Access**: Available via `.github/tools/Test-OpenSSHFunctionality.ps1` + - **Usage**: `.github/tools/Test-OpenSSHFunctionality.ps1` + - **If tool unavailable**: ERROR - This tool is required for the merge workflow + +4. **Git** - Version control operations - Cherry-pick: `git cherry-pick start_commit^..end_commit` (inclusive) - Status: `git status` - Remotes: `origin`, `upstream-pwsh`, `upstream` diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md index 82f267f30cd9..fdd4549f207b 100644 --- a/.github/instructions/build.instructions.md +++ b/.github/instructions/build.instructions.md @@ -64,27 +64,6 @@ The repository includes MCP tools that automate the build, verification, and err .\.github\tools\Test-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 ``` -### Using PowerShell Module Directly (Alternative) - -If you need direct access to the build module functions: - -#### Step 1: Environment Setup -```pwsh -# Navigate to repository root -cd - -# Import build helper module -Import-Module .\contrib\win32\openssh\OpenSSHBuildHelper.psm1 -Force - -# Verify module loaded -Get-Command -Module OpenSSHBuildHelper -``` - -#### Step 2: Initial Build -```pwsh -Start-OpenSSHBuild -Configuration Release -NativeHostArch x64 -``` - ## Compilation Error Resolution ### Common Error Categories @@ -255,17 +234,6 @@ contrib\win32\openssh\ - scp.exe, sftp.exe, sftp-server.exe - ssh-pkcs11-helper.exe, ssh-shellhost.exe, ssh-sk-helper.exe -### Manual Verification (Alternative) -```pwsh -# Check that all expected binaries were built -$buildPath = ".\contrib\win32\openssh\x64\Release" -Get-ChildItem $buildPath -Filter "*.exe" | Select-Object Name - -# Expected outputs: -# a binary for each vcxproj file, e.g. -# ssh.vcxproj -> ssh.exe -``` - ### Quick Functionality Test ```pwsh # Verify version reporting diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index fd8cce4b2620..d06bda0f986b 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -114,7 +114,6 @@ The process consists of several interconnected phases: git cherry-pick $commit # Build after every commit so we can attribute failures precisely. - # Prefer the MCP build helper if available; otherwise use Start-OpenSSHBuild. .\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 } ``` From 36c482653550d3d42c46e82e2fe7c7cf8fba088b Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 12:22:56 -0800 Subject: [PATCH 35/68] fix default log location in tool --- .github/tools/Test-OpenSSHBuild.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/tools/Test-OpenSSHBuild.ps1 b/.github/tools/Test-OpenSSHBuild.ps1 index 25aa95f4571c..afb978b778bc 100644 --- a/.github/tools/Test-OpenSSHBuild.ps1 +++ b/.github/tools/Test-OpenSSHBuild.ps1 @@ -142,7 +142,7 @@ try { $warnings = @() if (-not $LogFile) { - $LogFile = Join-Path $repoRoot "OpenSSH$Configuration$Architecture.log" + $LogFile = Join-Path $repoRoot "contrib\win32\openssh\OpenSSH$Configuration$Architecture.log" } if (Test-Path $LogFile) { From 2ffcb0e3aedfef3b088b44889c87cabd74f5e9b8 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 12:30:14 -0800 Subject: [PATCH 36/68] create tool for merge process prerequisites --- .github/agents/merge-upstream.agent.md | 34 +- .../merge-process-overview.instructions.md | 20 +- .github/tools/Test-MergePrerequisites.ps1 | 332 ++++++++++++++++++ 3 files changed, 374 insertions(+), 12 deletions(-) create mode 100644 .github/tools/Test-MergePrerequisites.ps1 diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index 9014f07e45e5..e5cca4d7b48e 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -82,17 +82,35 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for **Objective:** Verify environment and establish baseline **Steps:** -1. Verify Git, PowerShell, Visual Studio are available -2. Confirm repository remotes are configured -3. Identify upstream version/tag to merge -4. Create merge branch: `merge-v-` -5. Perform baseline build from current branch -6. Use Get-CommitGroups with `-FirstChunkOnly -GroupByCIPresence` to get first commit batch +1. **Run prerequisite verification via MCP tool:** + - **MCP Tool Name**: `mcp_test-server_Test_MergePrerequisites` + - **Parameters**: + - `TargetVersion` (string, required): Upstream version/tag to merge (e.g., "V_10_0_P2") + - `SkipBaselineBuild` (boolean, optional): Skip baseline build check (default: false) + + This single tool verifies: + - Git, PowerShell, Visual Studio are available + - Repository remotes are configured (origin, upstream, upstream-pwsh) + - Target version/tag exists in upstream + - Working directory is clean (no uncommitted changes) + - Baseline build passes from current branch + - First commit batch is identified + +2. **Proceed only if tool reports success:** + - Tool must return `Success: true` + - Tool must display "ALL PREREQUISITES MET" + - If tool fails, fix reported issues before continuing + +3. **Create merge branch:** + ```pwsh + git checkout -b merge-v- + # Example: git checkout -b merge-v10.0P2-20260109 + ``` **Success Criteria:** -- Base branch builds successfully -- First commit group identified (ending with any CI run) +- Prerequisite tool reports all checks passed - Merge branch created +- Ready to begin Phase 2 (cherry-picking first batch) ### Phase 2: Incremental Merge **Objective:** Cherry-pick commits in a single batch ending with a CI run diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index d06bda0f986b..491d36b3dd15 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -47,11 +47,23 @@ The process consists of several interconnected phases: ## Merge Phase ### Initial Preparation -1. **Verify baseline build** - **(📖 Detailed Instructions:** [Build Instructions](../build.instructions.md)): +1. **Verify prerequisites and baseline** + Use the Test-MergePrerequisites MCP tool: ```pwsh - Import-Module .\contrib\win32\openssh\OpenSSHBuildHelper.psm1 -Force - Start-OpenSSHBuild -Configuration Release -NativeHostArch x64 + # Run prerequisite check via MCP tool + # MCP Tool Name: mcp_test-server_Test_MergePrerequisites + # Parameters: TargetVersion (required), SkipBaselineBuild (optional) + + # Example invocation (replace with target like "V_10_0_P2"): + # The MCP tool will verify: + # - Git, PowerShell, Visual Studio installed + # - Repository remotes configured (origin, upstream, upstream-pwsh) + # - Target version exists in upstream + # - Working directory is clean + # - Baseline build passes (unless SkipBaselineBuild is true) + # - First commit batch identified + + # Proceed only if tool reports "ALL PREREQUISITES MET" ``` 2. **Configure git:** diff --git a/.github/tools/Test-MergePrerequisites.ps1 b/.github/tools/Test-MergePrerequisites.ps1 new file mode 100644 index 000000000000..c27c9bdb6dd5 --- /dev/null +++ b/.github/tools/Test-MergePrerequisites.ps1 @@ -0,0 +1,332 @@ +<# +.SYNOPSIS + MCP tool that verifies all prerequisites for starting an OpenSSH upstream merge. + +.DESCRIPTION + This script performs comprehensive pre-merge setup verification for the OpenSSH + upstream merge workflow. It checks: + - Required tools (Git, PowerShell, Visual Studio) + - Repository configuration (remotes, branch state) + - Baseline build capability + - Target version identification + + This tool should be run before starting Phase 1 of the merge workflow to ensure + all prerequisites are met. It prevents wasted effort by catching configuration + issues early. + +.PARAMETER TargetVersion + The upstream version/tag to merge (e.g., "V_10_0_P2", "V_9_9_P1") + This can be a tag name or branch name from the upstream repository. + +.PARAMETER SkipBaselineBuild + Skip the baseline build verification step. Use this if you've already + verified the base branch builds successfully. + Default: false (baseline build is performed) + +.OUTPUTS + Returns a hashtable with: + - Success: Boolean indicating all prerequisites passed + - GitInstalled: Boolean - Git is available + - PowerShellVersion: String - PowerShell version + - VSInstalled: Boolean - Visual Studio is available + - RemotesConfigured: Boolean - All required remotes configured + - TargetExists: Boolean - Target version/tag exists in upstream + - WorkingDirClean: Boolean - No uncommitted changes + - BaselineBuildPassed: Boolean - Base branch builds successfully (if not skipped) + - FirstChunkIdentified: Boolean - First commit batch identified + - FirstChunk: Object - First commit batch details from Get-CommitGroups + - Issues: Array - List of any issues found + - Message: String - Summary message + +.EXAMPLE + .\Test-MergePrerequisites.ps1 -TargetVersion "V_10_0_P2" + + Verifies all prerequisites for merging upstream V_10_0_P2. + +.EXAMPLE + .\Test-MergePrerequisites.ps1 -TargetVersion "V_10_0_P2" -SkipBaselineBuild + + Verifies prerequisites but skips the baseline build check. + +.NOTES + - This is a Phase 1 Pre-Merge Setup verification tool + - Should be run before creating the merge branch + - Requires approximately 2-5 minutes to complete (depending on baseline build) + - Part of the OpenSSH upstream merge workflow automation +#> + +param( + [Parameter(Mandatory=$true)] + [string]$TargetVersion, + + [Parameter(Mandatory=$false)] + [switch]$SkipBaselineBuild +) + +$scriptRoot = $PSScriptRoot +$repoRoot = (Get-Item $scriptRoot).Parent.Parent.FullName + +$result = @{ + Success = $false + GitInstalled = $false + PowerShellVersion = $PSVersionTable.PSVersion.ToString() + VSInstalled = $false + RemotesConfigured = $false + TargetExists = $false + WorkingDirClean = $false + BaselineBuildPassed = $null # null if skipped + FirstChunkIdentified = $false + FirstChunk = $null + Issues = @() + Message = "" +} + +try { + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "OpenSSH Merge Prerequisites Check" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "Target Version: $TargetVersion" -ForegroundColor White + Write-Host "Skip Baseline: $SkipBaselineBuild" -ForegroundColor White + Write-Host "========================================`n" -ForegroundColor Cyan + + # Change to repository root + Push-Location $repoRoot + + # Step 1: Verify Git + Write-Host "[1/8] Checking Git installation..." -ForegroundColor Cyan + try { + $gitVersion = git --version 2>&1 + if ($LASTEXITCODE -eq 0) { + $result.GitInstalled = $true + Write-Host " ✓ Git installed: $gitVersion" -ForegroundColor Green + } else { + $result.Issues += "Git is not installed or not in PATH" + Write-Host " ✗ Git not found" -ForegroundColor Red + } + } catch { + $result.Issues += "Git is not installed or not in PATH" + Write-Host " ✗ Git not found" -ForegroundColor Red + } + + # Step 2: Verify PowerShell version + Write-Host "`n[2/8] Checking PowerShell version..." -ForegroundColor Cyan + $psVersion = $PSVersionTable.PSVersion + if ($psVersion.Major -ge 5) { + Write-Host " ✓ PowerShell $($psVersion.ToString()) (>= 5.0)" -ForegroundColor Green + } else { + $result.Issues += "PowerShell version $($psVersion.ToString()) is too old (need >= 5.0)" + Write-Host " ✗ PowerShell $($psVersion.ToString()) is too old (need >= 5.0)" -ForegroundColor Red + } + + # Step 3: Verify Visual Studio + Write-Host "`n[3/8] Checking Visual Studio installation..." -ForegroundColor Cyan + $msBuildPaths = @( + "${env:ProgramFiles}\Microsoft Visual Studio\2022\*\MSBuild\Current\Bin\MSBuild.exe", + "${env:ProgramFiles}\Microsoft Visual Studio\2019\*\MSBuild\Current\Bin\MSBuild.exe", + "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\*\MSBuild\Current\Bin\MSBuild.exe", + "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\*\MSBuild\Current\Bin\MSBuild.exe" + ) + + $msBuildFound = $false + foreach ($path in $msBuildPaths) { + $resolved = Resolve-Path $path -ErrorAction SilentlyContinue + if ($resolved) { + $msBuildFound = $true + $result.VSInstalled = $true + Write-Host " ✓ Visual Studio found: $($resolved.Path)" -ForegroundColor Green + break + } + } + + if (-not $msBuildFound) { + $result.Issues += "Visual Studio 2019 or later not found" + Write-Host " ✗ Visual Studio 2019 or later not found" -ForegroundColor Red + } + + # Step 4: Verify repository remotes + Write-Host "`n[4/8] Checking repository remotes..." -ForegroundColor Cyan + if ($result.GitInstalled) { + $remotes = git remote 2>&1 + $expectedRemotes = @('origin', 'upstream', 'upstream-pwsh') + $missingRemotes = @() + + foreach ($remote in $expectedRemotes) { + if ($remotes -contains $remote) { + $remoteUrl = git remote get-url $remote 2>&1 + Write-Host " ✓ $remote configured: $remoteUrl" -ForegroundColor Green + } else { + $missingRemotes += $remote + Write-Host " ✗ $remote not configured" -ForegroundColor Red + } + } + + if ($missingRemotes.Count -eq 0) { + $result.RemotesConfigured = $true + } else { + $result.Issues += "Missing remotes: $($missingRemotes -join ', ')" + } + } else { + Write-Host " ⊘ Skipped (Git not available)" -ForegroundColor Yellow + } + + # Step 5: Verify target version exists + Write-Host "`n[5/8] Checking target version exists..." -ForegroundColor Cyan + if ($result.GitInstalled -and $result.RemotesConfigured) { + Write-Host " Fetching from upstream..." -ForegroundColor Gray + git fetch upstream 2>&1 | Out-Null + + $tagExists = git rev-parse "upstream/$TargetVersion" 2>&1 + if ($LASTEXITCODE -eq 0) { + $result.TargetExists = $true + Write-Host " ✓ Target exists: upstream/$TargetVersion" -ForegroundColor Green + } else { + $result.Issues += "Target version/tag 'upstream/$TargetVersion' not found" + Write-Host " ✗ Target 'upstream/$TargetVersion' not found" -ForegroundColor Red + } + } else { + Write-Host " ⊘ Skipped (prerequisites not met)" -ForegroundColor Yellow + } + + # Step 6: Verify working directory is clean + Write-Host "`n[6/8] Checking working directory state..." -ForegroundColor Cyan + if ($result.GitInstalled) { + $status = git status --porcelain 2>&1 + if ([string]::IsNullOrWhiteSpace($status)) { + $result.WorkingDirClean = $true + Write-Host " ✓ Working directory is clean" -ForegroundColor Green + } else { + $result.Issues += "Working directory has uncommitted changes" + Write-Host " ✗ Working directory has uncommitted changes" -ForegroundColor Red + Write-Host " Run 'git status' to see changes" -ForegroundColor Yellow + } + } else { + Write-Host " ⊘ Skipped (Git not available)" -ForegroundColor Yellow + } + + # Step 7: Baseline build verification + Write-Host "`n[7/8] Verifying baseline build..." -ForegroundColor Cyan + if ($SkipBaselineBuild) { + Write-Host " ⊘ Skipped (SkipBaselineBuild flag set)" -ForegroundColor Yellow + } else { + $currentBranch = git rev-parse --abbrev-ref HEAD 2>&1 + Write-Host " Current branch: $currentBranch" -ForegroundColor Gray + Write-Host " Starting baseline build..." -ForegroundColor Gray + + $buildScriptPath = Join-Path $scriptRoot "Build-OpenSSH.ps1" + $buildResult = & $buildScriptPath -Configuration Release -Architecture x64 + + if ($buildResult.OverallSuccess) { + $result.BaselineBuildPassed = $true + Write-Host " ✓ Baseline build passed" -ForegroundColor Green + Write-Host " All $($buildResult.ExpectedArtifacts) artifacts built successfully" -ForegroundColor Gray + } else { + $result.BaselineBuildPassed = $false + $result.Issues += "Baseline build failed: $($buildResult.Message)" + Write-Host " ✗ Baseline build failed" -ForegroundColor Red + Write-Host " $($buildResult.Message)" -ForegroundColor Yellow + Write-Host " Check build log: $($buildResult.LogFile)" -ForegroundColor Yellow + } + } + + # Step 8: Identify first commit batch + Write-Host "`n[8/8] Identifying first commit batch..." -ForegroundColor Cyan + if ($result.GitInstalled -and $result.TargetExists) { + $commitGroupsScript = Join-Path $scriptRoot "Get-CommitGroups.ps1" + + Write-Host " Finding last merged tag..." -ForegroundColor Gray + # Find the last upstream tag in current branch + $lastTag = git describe --tags --match "V_*" --abbrev=0 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host " Last merged tag: $lastTag" -ForegroundColor Gray + Write-Host " Finding commits from $lastTag to $TargetVersion..." -ForegroundColor Gray + + try { + $firstChunk = & $commitGroupsScript -GitHubTag $lastTag -FirstChunkOnly -GroupByCIPresence + + if ($firstChunk -and $firstChunk.CommitCount -gt 0) { + $result.FirstChunkIdentified = $true + $result.FirstChunk = $firstChunk + Write-Host " ✓ First batch identified" -ForegroundColor Green + Write-Host " Range: $($firstChunk.StartCommit)..$($firstChunk.EndCommit)" -ForegroundColor Gray + Write-Host " Commits: $($firstChunk.CommitCount)" -ForegroundColor Gray + Write-Host " Start: $($firstChunk.StartMessage)" -ForegroundColor Gray + Write-Host " End: $($firstChunk.EndMessage)" -ForegroundColor Gray + } else { + $result.Issues += "No commits found between $lastTag and $TargetVersion" + Write-Host " ⚠ No new commits found (may already be up to date)" -ForegroundColor Yellow + } + } catch { + $result.Issues += "Failed to get commit groups: $($_.Exception.Message)" + Write-Host " ✗ Failed to get commit groups" -ForegroundColor Red + Write-Host " $($_.Exception.Message)" -ForegroundColor Yellow + } + } else { + $result.Issues += "Could not find last merged tag (no V_* tags in current branch)" + Write-Host " ✗ Could not find last merged tag" -ForegroundColor Red + } + } else { + Write-Host " ⊘ Skipped (prerequisites not met)" -ForegroundColor Yellow + } + + # Final evaluation + Write-Host "`n========================================" -ForegroundColor Cyan + Write-Host "PREREQUISITE CHECK SUMMARY" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + + $criticalChecks = @( + $result.GitInstalled, + $result.VSInstalled, + $result.RemotesConfigured, + $result.TargetExists, + $result.WorkingDirClean + ) + + # Baseline build is critical unless skipped + if (-not $SkipBaselineBuild) { + $criticalChecks += $result.BaselineBuildPassed + } + + # First chunk identification is informational, not critical + $result.Success = ($criticalChecks | Where-Object { $_ -eq $false }).Count -eq 0 + + if ($result.Success) { + Write-Host "✓ ALL PREREQUISITES MET" -ForegroundColor Green + Write-Host "`nYou are ready to begin the merge process:" -ForegroundColor White + Write-Host " 1. Create merge branch: git checkout -b merge-v$TargetVersion-$(Get-Date -Format 'yyyyMMdd')" -ForegroundColor Gray + Write-Host " 2. Begin cherry-picking commits from first batch" -ForegroundColor Gray + + if ($result.FirstChunkIdentified) { + Write-Host "`nFirst batch to merge:" -ForegroundColor White + Write-Host " git cherry-pick $($result.FirstChunk.StartCommitFull)^..$($result.FirstChunk.EndCommitFull)" -ForegroundColor Gray + } + + $result.Message = "All prerequisites met. Ready to start merge." + } else { + Write-Host "✗ PREREQUISITES NOT MET" -ForegroundColor Red + Write-Host "`nIssues found:" -ForegroundColor Yellow + foreach ($issue in $result.Issues) { + Write-Host " • $issue" -ForegroundColor Red + } + $result.Message = "$($result.Issues.Count) issue(s) found. Fix issues before starting merge." + } + + Write-Host "" + + return $result + +} catch { + Write-Host "`n========================================" -ForegroundColor Red + Write-Host "ERROR" -ForegroundColor Red + Write-Host "========================================" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + Write-Host $_.ScriptStackTrace -ForegroundColor Gray + + $result.Success = $false + $result.Issues += "Tool error: $($_.Exception.Message)" + $result.Message = "Prerequisite check failed with error: $($_.Exception.Message)" + + return $result +} finally { + Pop-Location +} From 4984b21ecd7e1b4bb41f0142483593cadcd37313 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 12:44:19 -0800 Subject: [PATCH 37/68] replace more direct scripts references to mcp tool references --- .github/agents/merge-upstream.agent.md | 76 +++++++++-------- .github/instructions/build.instructions.md | 71 ++++++++-------- .../merge-process-overview.instructions.md | 85 ++++++++++--------- .github/instructions/testing.instructions.md | 62 ++++++++++---- 4 files changed, 167 insertions(+), 127 deletions(-) diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index e5cca4d7b48e..da1c415110ce 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -25,7 +25,7 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for 1. **Get-CommitGroups MCP Tool** - Groups commits by CI presence or success - **Access**: Available via MCP server - - **MCP Tool Name**: `mcp_pwsh-mcp-server_Get_CommitGroups` + - **MCP Tool Name**: `mcp_openssh-server_Get_CommitGroups` - **Parameters**: - `GitHubTag` (string, optional): GitHub tag to start from (e.g., "V_10_0_P2") - `StartCommit` (string, optional): Commit SHA to start from @@ -36,13 +36,20 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - **If tool unavailable**: ERROR - This tool is required for the merge workflow 2. **Build-OpenSSH MCP Tool** - Build automation and verification - - **Access**: Available via `.github/tools/Build-OpenSSH.ps1` - - **Usage**: `.github/tools/Build-OpenSSH.ps1 -Configuration Release -Architecture x64` + - **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` + - **Parameters**: + - `Configuration` (string, optional): Build configuration - "Debug" or "Release" (default: "Release") + - `Architecture` (string, optional): Target architecture - "x64", "x86", "ARM", "ARM64" (default: "x64") + - `Clean` (boolean, optional): Perform clean build (default: false) - **If tool unavailable**: ERROR - This tool is required for the merge workflow 3. **Test-OpenSSHFunctionality MCP Tool** - Functional testing - - **Access**: Available via `.github/tools/Test-OpenSSHFunctionality.ps1` - - **Usage**: `.github/tools/Test-OpenSSHFunctionality.ps1` + - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **Parameters**: + - `Configuration` (string, optional): Build configuration - "Debug" or "Release" (default: "Release") + - `Architecture` (string, optional): Target architecture - "x64", "x86", "ARM", "ARM64" (default: "x64") + - `SkipFirewall` (boolean, optional): Skip firewall configuration (default: false) + - `NoCleanup` (boolean, optional): Skip cleanup for debugging (default: false) - **If tool unavailable**: ERROR - This tool is required for the merge workflow 4. **Git** - Version control operations @@ -83,7 +90,7 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for **Steps:** 1. **Run prerequisite verification via MCP tool:** - - **MCP Tool Name**: `mcp_test-server_Test_MergePrerequisites` + - **MCP Tool Name**: `mcp_openssh-server_Test_MergePrerequisites` - **Parameters**: - `TargetVersion` (string, required): Upstream version/tag to merge (e.g., "V_10_0_P2") - `SkipBaselineBuild` (boolean, optional): Skip baseline build check (default: false) @@ -116,25 +123,24 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for **Objective:** Cherry-pick commits in a single batch ending with a CI run **Steps:** -1. **Get first commit batch** using Get-CommitGroups with `-FirstChunkOnly -GroupByCIPresence`: - ```pwsh - # For first batch - start from last merged tag - .github\tools\Get-CommitGroups.ps1 -GitHubTag "V_10_0_P2" -FirstChunkOnly -GroupByCIPresence - - # For subsequent batches - start from last batch's end commit - .github\tools\Get-CommitGroups.ps1 -StartCommit "" -FirstChunkOnly -GroupByCIPresence +1. **Get first commit batch** using Get-CommitGroups MCP tool: + - **MCP Tool Name**: `mcp_openssh-serve_Get_CommitGroups` + - **Parameters**: + - For first batch: `GitHubTag="V_10_0_P2"`, `FirstChunkOnly=true`, `GroupByCIPresence=true` + - For subsequent batches: `StartCommit=""`, `FirstChunkOnly=true`, `GroupByCIPresence=true` - # The tool returns structured data: - # { - # "ChunkNumber": 1, - # "StartCommit": "609fe2c", - # "EndCommit": "6fb728d", - # "StartCommitFull": "609fe2cae2459d721ac11d23cd27b8a94397ef3c", - # "EndCommitFull": "6fb728df50c1afd338cb0223a84ce24579577eff", - # "CommitCount": 12, - # "StartMessage": "upstream: rework the text for -3 to make it clearer", - # "EndMessage": "Run all tests on Cygwin again." - # } + **The tool returns structured data:** + ```json + { + "ChunkNumber": 1, + "StartCommit": "609fe2c", + "EndCommit": "6fb728d", + "StartCommitFull": "609fe2cae2459d721ac11d23cd27b8a94397ef3c", + "EndCommitFull": "6fb728df50c1afd338cb0223a84ce24579577eff", + "CommitCount": 12, + "StartMessage": "upstream: rework the text for -3 to make it clearer", + "EndMessage": "Run all tests on Cygwin again." + } ``` 2. **Display batch information for verification:** @@ -164,9 +170,8 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for **Steps:** 1. **Build the merged code:** - ```pwsh - .github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 - ``` + - **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` + - **Parameters**: `Configuration="Release"`, `Architecture="x64"` 2. **If build fails, fix compilation errors:** - Document all compilation errors from build output @@ -180,12 +185,12 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - Look for commits ending with `CIStatus: "success"` in the detailed output 4. **If CI was successful, run validation tests:** - ```pwsh - .github\tools\Test-OpenSSHFunctionality.ps1 - ``` - - This test installs service, creates test user, validates SSH connectivity - - If tests fail, fix issues and commit fixes - - **Do not proceed** to next batch until tests pass + - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **Parameters**: (use defaults for Release/x64) + + This test installs service, creates test user, validates SSH connectivity. + If tests fail, fix issues and commit fixes. + **Do not proceed** to next batch until tests pass. 5. **If CI was not successful (or no CI), skip validation:** - Build success is sufficient to proceed @@ -261,9 +266,8 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - Summarize and get approval 3. **Continue** until all target commits are merged or HEAD is reached 4. **Perform final comprehensive validation:** - ```pwsh - .github\tools\Test-OpenSSHFunctionality.ps1 - ``` + - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **Parameters**: (use defaults for Release/x64) **Success Criteria:** - All commit batches processed diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md index fdd4549f207b..84b10974f4c0 100644 --- a/.github/instructions/build.instructions.md +++ b/.github/instructions/build.instructions.md @@ -31,16 +31,17 @@ git --version The repository includes MCP tools that automate the build, verification, and error analysis process. #### Option 1: Build & Verify (Recommended) -```pwsh -# Complete build and verification in one step -.\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 - -# Clean build -.\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 -Clean - -# Debug build -.\.github\tools\Build-OpenSSH.ps1 -Configuration Debug -Architecture x64 -``` +Use the Build-OpenSSH MCP tool: +- **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` +- **Parameters**: + - `Configuration` (optional): "Debug" or "Release" (default: "Release") + - `Architecture` (optional): "x64", "x86", "ARM", "ARM64" (default: "x64") + - `Clean` (optional): Perform clean build (default: false) + +**Examples:** +- Complete build and verification: `Configuration="Release"`, `Architecture="x64"` +- Clean build: `Configuration="Release"`, `Architecture="x64"`, `Clean=true` +- Debug build: `Configuration="Debug"`, `Architecture="x64"` **Output includes:** - Build success/failure status @@ -50,19 +51,21 @@ The repository includes MCP tools that automate the build, verification, and err - Build log location #### Option 2: Build Only -```pwsh -# Incremental build -.\.github\tools\Start-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 +Use the Start-OpenSSHBuild MCP tool: +- **MCP Tool Name**: `mcp_openssh-serve_Start_OpenSSHBuild` +- **Parameters**: + - `Configuration` (optional): "Debug" or "Release" (default: "Release") + - `Architecture` (optional): "x64", "x86", "ARM", "ARM64" (default: "x64") + - `Clean` (optional): Perform clean build (default: false) -# Clean build -.\.github\tools\Start-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 -Clean -``` +**Examples:** +- Incremental build: `Configuration="Release"`, `Architecture="x64"` +- Clean build: `Configuration="Release"`, `Architecture="x64"`, `Clean=true` #### Option 3: Test Existing Build -```pwsh -# Test artifacts and parse errors from previous build -.\.github\tools\Test-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 -``` +Use the Test-OpenSSHBuild MCP tool: +- **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHBuild` +- **Parameters**: `Configuration="Release"`, `Architecture="x64"` ## Compilation Error Resolution @@ -138,21 +141,21 @@ fatal error C1083: Cannot open source file: 'newfile.c' #### AI Agent Workflow: 1. **Attempt initial build using MCP tool** - ```pwsh - .\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 - ``` + - **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` + - **Parameters**: `Configuration="Release"`, `Architecture="x64"` 2. **Review structured error output** from Build-OpenSSH tool 3. **Categorize error types** (preprocessor, Windows compatibility, build system) 4. **Apply appropriate resolution strategy** (see error categories above) -5. **Rebuild and verify** using Build-OpenSSH tool again +5. **Rebuild and verify** using Build-OpenSSH MCP tool again 6. **Commit fixes with detailed message** #### Manual Error Analysis Commands (if needed): -```pwsh -# Parse errors manually from log file -.\.github\tools\Test-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 +Parse errors manually from log file using the Test-OpenSSHBuild MCP tool: +- **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHBuild` +- **Parameters**: `Configuration="Release"`, `Architecture="x64"` -# Direct MSBuild with detailed logging +Or use direct MSBuild with detailed logging: +```pwsh & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" .\contrib\win32\openssh\Win32-OpenSSH.sln /p:Configuration=Release /p:Platform=x64 /v:detailed ``` @@ -215,13 +218,13 @@ contrib\win32\openssh\ ### Build Verification Using MCP Tools ```pwsh -# Recommended: Use Build-OpenSSH which includes testing -.\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 - -# Or test an existing build -.\.github\tools\Test-OpenSSHBuild.ps1 -Configuration Release -Architecture x64 -``` +Use the Build-OpenSSH MCP tool (recommended): +- **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` +- **Parameters**: `Configuration="Release"`, `Architecture="x64"` +Or test an existing build: +- **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHBuild` +- **Parameters**: `Configuration="Release"`, `Architecture="x64" **Expected Output:** - Success/failure status - 14 of 14 artifacts found diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 491d36b3dd15..69083761a9fd 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -51,7 +51,7 @@ The process consists of several interconnected phases: Use the Test-MergePrerequisites MCP tool: ```pwsh # Run prerequisite check via MCP tool - # MCP Tool Name: mcp_test-server_Test_MergePrerequisites + # MCP Tool Name: mcp_openssh-server_Test_MergePrerequisites # Parameters: TargetVersion (required), SkipBaselineBuild (optional) # Example invocation (replace with target like "V_10_0_P2"): @@ -79,35 +79,41 @@ The process consists of several interconnected phases: ### Perform Merge with Grouped Commits 4. **Identify merge range and group commits:** - Use .\tools\Get-CommitGroups.ps1 with `-FirstChunkOnly -GroupByCIPresence` - ```pwsh - # Find the last upstream tag in the fork (this is the starting point for the next merge) - # Call .\tools\Get-CommitGroups.ps1 -GitHubTag -FirstChunkOnly -GroupByCIPresence - # This gets commits ending with any commit that has CI runs (not just successful CI) - # Example output: - # { - # "ChunkNumber": 1, - # "StartCommit": "609fe2c", - # "EndCommit": "6fb728d", - # "StartCommitFull": "609fe2cae2459d721ac11d23cd27b8a94397ef3c", - # "EndCommitFull": "6fb728df50c1afd338cb0223a84ce24579577eff", - # "CommitCount": 57, - # "StartMessage": "upstream: rework the text for -3 to make it clearer what default", - # "EndMessage": "Run all tests on Cygwin again." - # } - - # Print the commit batch details for verification - Write-Host "Processing batch: $($result.StartCommit)..$($result.EndCommit)" - Write-Host "Start: $($result.StartMessage)" - Write-Host "End: $($result.EndMessage)" - Write-Host "End Commit CI Status: $($result.EndCommit has CI runs)" - - # After completing steps below, get next batch: - # Call .\tools\Get-CommitGroups.ps1 -StartCommit -FirstChunkOnly -GroupByCIPresence - # Continue this process untiland - # follow the below steps to completion until the upstream's HEAD has been successfully merged + Use the Get-CommitGroups MCP tool with `-FirstChunkOnly -GroupByCIPresence` + + **MCP Tool Name**: `mcp_openssh-serve_Get_CommitGroups` + + **Parameters**: + - `GitHubTag` (string, optional): Start from last merged tag (e.g., "V_10_0_P2") + - `StartCommit` (string, optional): Start from specific commit SHA + - `FirstChunkOnly` (boolean): Set to `true` + - `GroupByCIPresence` (boolean): Set to `true` + + **Example for first batch**: + - Find the last upstream tag in the fork + - Call tool with `GitHubTag=`, `FirstChunkOnly=true`, `GroupByCIPresence=true` + - This gets commits ending with any commit that has CI runs (not just successful CI) + + **Example output:** + ```json + { + "ChunkNumber": 1, + "StartCommit": "609fe2c", + "EndCommit": "6fb728d", + "StartCommitFull": "609fe2cae2459d721ac11d23cd27b8a94397ef3c", + "EndCommitFull": "6fb728df50c1afd338cb0223a84ce24579577eff", + "CommitCount": 57, + "StartMessage": "upstream: rework the text for -3 to make it clearer what default", + "EndMessage": "Run all tests on Cygwin again." + } ``` + Display batch details for verification, then proceed with cherry-picking. + + **After completing steps below, get next batch**: + - Call tool with `StartCommit=`, `FirstChunkOnly=true`, `GroupByCIPresence=true` + - Continue this process until the upstream's HEAD has been successfully merged + 5. **Execute chunked merge (commit-by-commit within the chunk):** **Important:** Even though commits are *grouped* into chunks for planning/CI boundaries, **cherry-pick each commit individually** within the chunk and **build after each commit**. This catches Windows-specific build breaks early and makes it clear which upstream commit introduced a regression. @@ -126,7 +132,8 @@ The process consists of several interconnected phases: git cherry-pick $commit # Build after every commit so we can attribute failures precisely. - .\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 + # Use MCP Tool: mcp_openssh-serve_Build_OpenSSH + # Parameters: Configuration="Release", Architecture="x64" } ``` @@ -149,21 +156,19 @@ The process consists of several interconnected phases: ``` 9. **Build after completing the batch:** - ```pwsh - .\.github\tools\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 + Use the Build-OpenSSH MCP tool: + - **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` + - **Parameters**: `Configuration="Release"`, `Architecture="x64"` - # If build fails, fix issues and rebuild - # Commit any build fixes separately - ``` + If build fails, fix issues and rebuild. Commit any build fixes separately. 10. **Validate if batch ended with successful CI:** - ```pwsh - # Check the end commit's CI status from Get-CommitGroups output - # If CI was successful, run validation: - .\.github\tools\Test-OpenSSHFunctionality.ps1 + Check the end commit's CI status from Get-CommitGroups output. + If CI was successful, run validation: + - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **Parameters**: (use defaults for Release/x64) - # If no CI or failed CI, skip validation (build success is sufficient) - ``` + If no CI or failed CI, skip validation (build success is sufficient). 11. **Provide summary and get approval:** - Summarize batch changes, conflicts resolved, build status, validation status diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index 7aa715edeab2..00a60db8fc4d 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -13,16 +13,18 @@ This document provides comprehensive testing procedures for validating OpenSSH-P The repository includes an MCP tool that automates end-to-end functional testing of OpenSSH on Windows. -```pwsh -# Run automated functional test (requires Administrator privileges) -.\.github\tools\Test-OpenSSHFunctionality.ps1 - -# Test with specific configuration -.\.github\tools\Test-OpenSSHFunctionality.ps1 -Configuration Debug -Architecture x64 - -# Skip firewall configuration if rules already exist -.\.github\tools\Test-OpenSSHFunctionality.ps1 -SkipFirewall -``` +Use the Test-OpenSSHFunctionality MCP tool: +- **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` +- **Parameters**: + - `Configuration` (optional): "Debug" or "Release" (default: "Release") + - `Architecture` (optional): "x64", "x86", "ARM", "ARM64" (default: "x64") + - `SkipFirewall` (optional): Skip firewall configuration (default: false) + - `NoCleanup` (optional): Skip cleanup for debugging (default: false) + +**Examples:** +- Run with defaults: (no parameters needed) +- Test with specific configuration: `Configuration="Debug"`, `Architecture="x64"` +- Skip firewall configuration: `SkipFirewall=true` **What the tool does:** 1. Verifies Administrator privileges @@ -69,9 +71,36 @@ Status: PASSED - `Errors`: Array of any errors encountered - `Message`: Summary message -## Manual Testing Procedures +## Primary Testing Approach + +**Use the automated Test-OpenSSHFunctionality MCP tool for all testing.** + +The MCP tool performs comprehensive end-to-end testing including: +- Administrator privilege verification +- Temporary test user creation +- SSH service installation and startup +- Windows Firewall configuration +- SSH connection testing with password authentication +- Command execution verification +- Complete cleanup of all test resources + +**MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + +**Parameters**: +- `Configuration` (optional): "Debug" or "Release" (default: "Release") +- `Architecture` (optional): "x64", "x86", "ARM", "ARM64" (default: "x64") +- `SkipFirewall` (optional): Skip firewall configuration (default: false) +- `NoCleanup` (optional): Skip cleanup for debugging (default: false) + +**When to use**: +- After successful build to validate functionality +- During merge process at CI checkpoints +- Before creating pull requests +- When debugging SSH connectivity issues + +## Manual Testing Procedures (For Troubleshooting Only) -If you need to perform manual testing or troubleshoot specific issues, follow these procedures: +If the automated MCP tool fails and you need to troubleshoot specific issues manually, follow these procedures: ### Prerequisites Check ```pwsh @@ -197,7 +226,7 @@ Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*SSH*"} ## Success Criteria **Testing is successful when:** -- [ ] All expected executables are present after build (verified by Test-OpenSSHBuild.ps1) +- [ ] All expected executables are present after build (verified by Test-OpenSSHBuild MCP tool) - [ ] SSH service installs and starts without errors - [ ] SSH connection with password authentication succeeds - [ ] Test command executes successfully via SSH connection @@ -206,7 +235,7 @@ Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*SSH*"} ## AI Agent Guidelines -1. **Use automated testing tools** whenever possible - prefer Test-OpenSSHFunctionality.ps1 over manual procedures +1. **Use automated testing tools** whenever possible - use the Test-OpenSSHFunctionality MCP tool over manual procedures 2. **Run tests incrementally** during the merge process, not just at the end 3. **Document any test failures** and their resolutions in commit messages 4. **Pay special attention to Windows-specific functionality** that might be affected by upstream changes @@ -216,9 +245,8 @@ Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*SSH*"} ### Recommended Testing Workflow for AI Agents 1. **After successful build**, run the automated functionality test: - ```pwsh - .\.github\tools\Test-OpenSSHFunctionality.ps1 - ``` + - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **Parameters**: (use defaults) 2. **If test passes**, the merge is validated for basic SSH functionality From 63ac1acf50e0457881163d966c84cd86530e493e Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 13:00:53 -0800 Subject: [PATCH 38/68] update mcp server name --- .github/agents/merge-upstream.agent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index da1c415110ce..a45f950eb2cf 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -2,7 +2,7 @@ name: merge-upstream description: Assist with merging upstream OpenSSH commits into the PowerShell fork. tools: - ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'test-server/*', 'agent', 'todo'] + ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'openssh-server/*', 'agent', 'todo'] --- # OpenSSH Upstream Merge Agent From 644bbf55b5659aa86da2ad48200832a69dfbb036 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 13:05:38 -0800 Subject: [PATCH 39/68] fix merge prereq tool --- .github/tools/Test-MergePrerequisites.ps1 | 78 ++++++----------------- 1 file changed, 19 insertions(+), 59 deletions(-) diff --git a/.github/tools/Test-MergePrerequisites.ps1 b/.github/tools/Test-MergePrerequisites.ps1 index c27c9bdb6dd5..cb7877791d58 100644 --- a/.github/tools/Test-MergePrerequisites.ps1 +++ b/.github/tools/Test-MergePrerequisites.ps1 @@ -75,8 +75,6 @@ $result = @{ TargetExists = $false WorkingDirClean = $false BaselineBuildPassed = $null # null if skipped - FirstChunkIdentified = $false - FirstChunk = $null Issues = @() Message = "" } @@ -170,25 +168,33 @@ try { } # Step 5: Verify target version exists - Write-Host "`n[5/8] Checking target version exists..." -ForegroundColor Cyan + Write-Host "`n[5/7] Checking target version exists..." -ForegroundColor Cyan if ($result.GitInstalled -and $result.RemotesConfigured) { Write-Host " Fetching from upstream..." -ForegroundColor Gray - git fetch upstream 2>&1 | Out-Null + git fetch upstream --tags 2>&1 | Out-Null - $tagExists = git rev-parse "upstream/$TargetVersion" 2>&1 + # Check if it's a tag first, then try as a branch + $tagExists = git rev-parse "$TargetVersion" 2>&1 if ($LASTEXITCODE -eq 0) { $result.TargetExists = $true - Write-Host " ✓ Target exists: upstream/$TargetVersion" -ForegroundColor Green + Write-Host " ✓ Target tag exists: $TargetVersion" -ForegroundColor Green } else { - $result.Issues += "Target version/tag 'upstream/$TargetVersion' not found" - Write-Host " ✗ Target 'upstream/$TargetVersion' not found" -ForegroundColor Red + # Try as a branch + $branchExists = git rev-parse "upstream/$TargetVersion" 2>&1 + if ($LASTEXITCODE -eq 0) { + $result.TargetExists = $true + Write-Host " ✓ Target branch exists: upstream/$TargetVersion" -ForegroundColor Green + } else { + $result.Issues += "Target version/tag '$TargetVersion' not found in upstream" + Write-Host " ✗ Target '$TargetVersion' not found" -ForegroundColor Red + } } } else { Write-Host " ⊘ Skipped (prerequisites not met)" -ForegroundColor Yellow } # Step 6: Verify working directory is clean - Write-Host "`n[6/8] Checking working directory state..." -ForegroundColor Cyan + Write-Host "`n[6/7] Checking working directory state..." -ForegroundColor Cyan if ($result.GitInstalled) { $status = git status --porcelain 2>&1 if ([string]::IsNullOrWhiteSpace($status)) { @@ -204,7 +210,7 @@ try { } # Step 7: Baseline build verification - Write-Host "`n[7/8] Verifying baseline build..." -ForegroundColor Cyan + Write-Host "`n[7/7] Verifying baseline build..." -ForegroundColor Cyan if ($SkipBaselineBuild) { Write-Host " ⊘ Skipped (SkipBaselineBuild flag set)" -ForegroundColor Yellow } else { @@ -228,47 +234,6 @@ try { } } - # Step 8: Identify first commit batch - Write-Host "`n[8/8] Identifying first commit batch..." -ForegroundColor Cyan - if ($result.GitInstalled -and $result.TargetExists) { - $commitGroupsScript = Join-Path $scriptRoot "Get-CommitGroups.ps1" - - Write-Host " Finding last merged tag..." -ForegroundColor Gray - # Find the last upstream tag in current branch - $lastTag = git describe --tags --match "V_*" --abbrev=0 2>&1 - - if ($LASTEXITCODE -eq 0) { - Write-Host " Last merged tag: $lastTag" -ForegroundColor Gray - Write-Host " Finding commits from $lastTag to $TargetVersion..." -ForegroundColor Gray - - try { - $firstChunk = & $commitGroupsScript -GitHubTag $lastTag -FirstChunkOnly -GroupByCIPresence - - if ($firstChunk -and $firstChunk.CommitCount -gt 0) { - $result.FirstChunkIdentified = $true - $result.FirstChunk = $firstChunk - Write-Host " ✓ First batch identified" -ForegroundColor Green - Write-Host " Range: $($firstChunk.StartCommit)..$($firstChunk.EndCommit)" -ForegroundColor Gray - Write-Host " Commits: $($firstChunk.CommitCount)" -ForegroundColor Gray - Write-Host " Start: $($firstChunk.StartMessage)" -ForegroundColor Gray - Write-Host " End: $($firstChunk.EndMessage)" -ForegroundColor Gray - } else { - $result.Issues += "No commits found between $lastTag and $TargetVersion" - Write-Host " ⚠ No new commits found (may already be up to date)" -ForegroundColor Yellow - } - } catch { - $result.Issues += "Failed to get commit groups: $($_.Exception.Message)" - Write-Host " ✗ Failed to get commit groups" -ForegroundColor Red - Write-Host " $($_.Exception.Message)" -ForegroundColor Yellow - } - } else { - $result.Issues += "Could not find last merged tag (no V_* tags in current branch)" - Write-Host " ✗ Could not find last merged tag" -ForegroundColor Red - } - } else { - Write-Host " ⊘ Skipped (prerequisites not met)" -ForegroundColor Yellow - } - # Final evaluation Write-Host "`n========================================" -ForegroundColor Cyan Write-Host "PREREQUISITE CHECK SUMMARY" -ForegroundColor Cyan @@ -287,19 +252,14 @@ try { $criticalChecks += $result.BaselineBuildPassed } - # First chunk identification is informational, not critical $result.Success = ($criticalChecks | Where-Object { $_ -eq $false }).Count -eq 0 if ($result.Success) { Write-Host "✓ ALL PREREQUISITES MET" -ForegroundColor Green Write-Host "`nYou are ready to begin the merge process:" -ForegroundColor White - Write-Host " 1. Create merge branch: git checkout -b merge-v$TargetVersion-$(Get-Date -Format 'yyyyMMdd')" -ForegroundColor Gray - Write-Host " 2. Begin cherry-picking commits from first batch" -ForegroundColor Gray - - if ($result.FirstChunkIdentified) { - Write-Host "`nFirst batch to merge:" -ForegroundColor White - Write-Host " git cherry-pick $($result.FirstChunk.StartCommitFull)^..$($result.FirstChunk.EndCommitFull)" -ForegroundColor Gray - } + Write-Host " 1. Create merge branch: git checkout -b merge-$TargetVersion-$(Get-Date -Format 'yyyyMMdd')" -ForegroundColor Gray + Write-Host " 2. Use Get-CommitGroups tool to identify first batch of commits" -ForegroundColor Gray + Write-Host " 3. Begin cherry-picking commits from first batch" -ForegroundColor Gray $result.Message = "All prerequisites met. Ready to start merge." } else { From 1d8b2015110e8f44cfc5f3c373e42f1a5b8625f8 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 13:16:40 -0800 Subject: [PATCH 40/68] update merge prereq tool --- .github/tools/Test-MergePrerequisites.ps1 | 70 +++++------------------ 1 file changed, 13 insertions(+), 57 deletions(-) diff --git a/.github/tools/Test-MergePrerequisites.ps1 b/.github/tools/Test-MergePrerequisites.ps1 index cb7877791d58..8ae3b1f3f3da 100644 --- a/.github/tools/Test-MergePrerequisites.ps1 +++ b/.github/tools/Test-MergePrerequisites.ps1 @@ -7,22 +7,19 @@ upstream merge workflow. It checks: - Required tools (Git, PowerShell, Visual Studio) - Repository configuration (remotes, branch state) - - Baseline build capability - Target version identification This tool should be run before starting Phase 1 of the merge workflow to ensure all prerequisites are met. It prevents wasted effort by catching configuration issues early. + + To verify baseline build capability, use the Test-OpenSSHBuild MCP tool: + mcp_openssh-serve_Test_OpenSSHBuild .PARAMETER TargetVersion The upstream version/tag to merge (e.g., "V_10_0_P2", "V_9_9_P1") This can be a tag name or branch name from the upstream repository. -.PARAMETER SkipBaselineBuild - Skip the baseline build verification step. Use this if you've already - verified the base branch builds successfully. - Default: false (baseline build is performed) - .OUTPUTS Returns a hashtable with: - Success: Boolean indicating all prerequisites passed @@ -32,9 +29,6 @@ - RemotesConfigured: Boolean - All required remotes configured - TargetExists: Boolean - Target version/tag exists in upstream - WorkingDirClean: Boolean - No uncommitted changes - - BaselineBuildPassed: Boolean - Base branch builds successfully (if not skipped) - - FirstChunkIdentified: Boolean - First commit batch identified - - FirstChunk: Object - First commit batch details from Get-CommitGroups - Issues: Array - List of any issues found - Message: String - Summary message @@ -42,25 +36,18 @@ .\Test-MergePrerequisites.ps1 -TargetVersion "V_10_0_P2" Verifies all prerequisites for merging upstream V_10_0_P2. - -.EXAMPLE - .\Test-MergePrerequisites.ps1 -TargetVersion "V_10_0_P2" -SkipBaselineBuild - - Verifies prerequisites but skips the baseline build check. + For baseline build verification, use Test-OpenSSHBuild MCP tool separately. .NOTES - This is a Phase 1 Pre-Merge Setup verification tool - Should be run before creating the merge branch - - Requires approximately 2-5 minutes to complete (depending on baseline build) + - For baseline build verification, use: mcp_openssh-serve_Test_OpenSSHBuild - Part of the OpenSSH upstream merge workflow automation #> param( [Parameter(Mandatory=$true)] - [string]$TargetVersion, - - [Parameter(Mandatory=$false)] - [switch]$SkipBaselineBuild + [string]$TargetVersion ) $scriptRoot = $PSScriptRoot @@ -74,7 +61,6 @@ $result = @{ RemotesConfigured = $false TargetExists = $false WorkingDirClean = $false - BaselineBuildPassed = $null # null if skipped Issues = @() Message = "" } @@ -84,14 +70,13 @@ try { Write-Host "OpenSSH Merge Prerequisites Check" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan Write-Host "Target Version: $TargetVersion" -ForegroundColor White - Write-Host "Skip Baseline: $SkipBaselineBuild" -ForegroundColor White Write-Host "========================================`n" -ForegroundColor Cyan # Change to repository root Push-Location $repoRoot # Step 1: Verify Git - Write-Host "[1/8] Checking Git installation..." -ForegroundColor Cyan + Write-Host "[1/6] Checking Git installation..." -ForegroundColor Cyan try { $gitVersion = git --version 2>&1 if ($LASTEXITCODE -eq 0) { @@ -107,7 +92,7 @@ try { } # Step 2: Verify PowerShell version - Write-Host "`n[2/8] Checking PowerShell version..." -ForegroundColor Cyan + Write-Host "`n[2/6] Checking PowerShell version..." -ForegroundColor Cyan $psVersion = $PSVersionTable.PSVersion if ($psVersion.Major -ge 5) { Write-Host " ✓ PowerShell $($psVersion.ToString()) (>= 5.0)" -ForegroundColor Green @@ -117,7 +102,7 @@ try { } # Step 3: Verify Visual Studio - Write-Host "`n[3/8] Checking Visual Studio installation..." -ForegroundColor Cyan + Write-Host "`n[3/6] Checking Visual Studio installation..." -ForegroundColor Cyan $msBuildPaths = @( "${env:ProgramFiles}\Microsoft Visual Studio\2022\*\MSBuild\Current\Bin\MSBuild.exe", "${env:ProgramFiles}\Microsoft Visual Studio\2019\*\MSBuild\Current\Bin\MSBuild.exe", @@ -142,7 +127,7 @@ try { } # Step 4: Verify repository remotes - Write-Host "`n[4/8] Checking repository remotes..." -ForegroundColor Cyan + Write-Host "`n[4/6] Checking repository remotes..." -ForegroundColor Cyan if ($result.GitInstalled) { $remotes = git remote 2>&1 $expectedRemotes = @('origin', 'upstream', 'upstream-pwsh') @@ -168,7 +153,7 @@ try { } # Step 5: Verify target version exists - Write-Host "`n[5/7] Checking target version exists..." -ForegroundColor Cyan + Write-Host "`n[5/6] Checking target version exists..." -ForegroundColor Cyan if ($result.GitInstalled -and $result.RemotesConfigured) { Write-Host " Fetching from upstream..." -ForegroundColor Gray git fetch upstream --tags 2>&1 | Out-Null @@ -194,7 +179,7 @@ try { } # Step 6: Verify working directory is clean - Write-Host "`n[6/7] Checking working directory state..." -ForegroundColor Cyan + Write-Host "`n[6/6] Checking working directory state..." -ForegroundColor Cyan if ($result.GitInstalled) { $status = git status --porcelain 2>&1 if ([string]::IsNullOrWhiteSpace($status)) { @@ -209,31 +194,6 @@ try { Write-Host " ⊘ Skipped (Git not available)" -ForegroundColor Yellow } - # Step 7: Baseline build verification - Write-Host "`n[7/7] Verifying baseline build..." -ForegroundColor Cyan - if ($SkipBaselineBuild) { - Write-Host " ⊘ Skipped (SkipBaselineBuild flag set)" -ForegroundColor Yellow - } else { - $currentBranch = git rev-parse --abbrev-ref HEAD 2>&1 - Write-Host " Current branch: $currentBranch" -ForegroundColor Gray - Write-Host " Starting baseline build..." -ForegroundColor Gray - - $buildScriptPath = Join-Path $scriptRoot "Build-OpenSSH.ps1" - $buildResult = & $buildScriptPath -Configuration Release -Architecture x64 - - if ($buildResult.OverallSuccess) { - $result.BaselineBuildPassed = $true - Write-Host " ✓ Baseline build passed" -ForegroundColor Green - Write-Host " All $($buildResult.ExpectedArtifacts) artifacts built successfully" -ForegroundColor Gray - } else { - $result.BaselineBuildPassed = $false - $result.Issues += "Baseline build failed: $($buildResult.Message)" - Write-Host " ✗ Baseline build failed" -ForegroundColor Red - Write-Host " $($buildResult.Message)" -ForegroundColor Yellow - Write-Host " Check build log: $($buildResult.LogFile)" -ForegroundColor Yellow - } - } - # Final evaluation Write-Host "`n========================================" -ForegroundColor Cyan Write-Host "PREREQUISITE CHECK SUMMARY" -ForegroundColor Cyan @@ -247,16 +207,12 @@ try { $result.WorkingDirClean ) - # Baseline build is critical unless skipped - if (-not $SkipBaselineBuild) { - $criticalChecks += $result.BaselineBuildPassed - } - $result.Success = ($criticalChecks | Where-Object { $_ -eq $false }).Count -eq 0 if ($result.Success) { Write-Host "✓ ALL PREREQUISITES MET" -ForegroundColor Green Write-Host "`nYou are ready to begin the merge process:" -ForegroundColor White + Write-Host " 0. (Optional) Verify baseline build: mcp_openssh-serve_Test_OpenSSHBuild" -ForegroundColor Gray Write-Host " 1. Create merge branch: git checkout -b merge-$TargetVersion-$(Get-Date -Format 'yyyyMMdd')" -ForegroundColor Gray Write-Host " 2. Use Get-CommitGroups tool to identify first batch of commits" -ForegroundColor Gray Write-Host " 3. Begin cherry-picking commits from first batch" -ForegroundColor Gray From aa02282770b2209298ce5911b9871e3f27819f08 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 13:17:45 -0800 Subject: [PATCH 41/68] update instructions --- .github/tools/Test-MergePrerequisites.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/tools/Test-MergePrerequisites.ps1 b/.github/tools/Test-MergePrerequisites.ps1 index 8ae3b1f3f3da..aeae000c83a9 100644 --- a/.github/tools/Test-MergePrerequisites.ps1 +++ b/.github/tools/Test-MergePrerequisites.ps1 @@ -212,7 +212,7 @@ try { if ($result.Success) { Write-Host "✓ ALL PREREQUISITES MET" -ForegroundColor Green Write-Host "`nYou are ready to begin the merge process:" -ForegroundColor White - Write-Host " 0. (Optional) Verify baseline build: mcp_openssh-serve_Test_OpenSSHBuild" -ForegroundColor Gray + Write-Host " 0. (Optional) Verify baseline build: mcp_openssh-server_Test_OpenSSHBuild" -ForegroundColor Gray Write-Host " 1. Create merge branch: git checkout -b merge-$TargetVersion-$(Get-Date -Format 'yyyyMMdd')" -ForegroundColor Gray Write-Host " 2. Use Get-CommitGroups tool to identify first batch of commits" -ForegroundColor Gray Write-Host " 3. Begin cherry-picking commits from first batch" -ForegroundColor Gray From 2bbb98e9c8e45a0af662e25c8ae2fabe5267205c Mon Sep 17 00:00:00 2001 From: User Date: Fri, 9 Jan 2026 13:21:27 -0800 Subject: [PATCH 42/68] update instructions --- .../instructions/merge/merge-process-overview.instructions.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 69083761a9fd..f9899a1cd993 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -60,8 +60,6 @@ The process consists of several interconnected phases: # - Repository remotes configured (origin, upstream, upstream-pwsh) # - Target version exists in upstream # - Working directory is clean - # - Baseline build passes (unless SkipBaselineBuild is true) - # - First commit batch identified # Proceed only if tool reports "ALL PREREQUISITES MET" ``` From 123f8e24b05b7a8d00ed0313272f39d730094e53 Mon Sep 17 00:00:00 2001 From: User Date: Thu, 15 Jan 2026 08:06:30 -0800 Subject: [PATCH 43/68] refactor tool to workaround io/buffer issues when invoked via MCP --- .github/tools/Test-MergePrerequisites.ps1 | 120 ++++++++++++---------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/.github/tools/Test-MergePrerequisites.ps1 b/.github/tools/Test-MergePrerequisites.ps1 index aeae000c83a9..879ef45dc424 100644 --- a/.github/tools/Test-MergePrerequisites.ps1 +++ b/.github/tools/Test-MergePrerequisites.ps1 @@ -51,6 +51,9 @@ param( ) $scriptRoot = $PSScriptRoot +if ([string]::IsNullOrEmpty($scriptRoot)) { + $scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +} $repoRoot = (Get-Item $scriptRoot).Parent.Parent.FullName $result = @{ @@ -70,18 +73,21 @@ try { Write-Host "OpenSSH Merge Prerequisites Check" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan Write-Host "Target Version: $TargetVersion" -ForegroundColor White + Write-Host "Repository Root: $repoRoot" -ForegroundColor Gray Write-Host "========================================`n" -ForegroundColor Cyan # Change to repository root Push-Location $repoRoot + Write-Host "Working Directory: $(Get-Location)" -ForegroundColor Gray + Write-Host "" # Step 1: Verify Git - Write-Host "[1/6] Checking Git installation..." -ForegroundColor Cyan + Write-Host "[1/5] Checking Git installation..." -ForegroundColor Cyan try { - $gitVersion = git --version 2>&1 - if ($LASTEXITCODE -eq 0) { + $gitPath = Get-Command git -ErrorAction SilentlyContinue + if ($gitPath) { $result.GitInstalled = $true - Write-Host " ✓ Git installed: $gitVersion" -ForegroundColor Green + Write-Host " ✓ Git found: $($gitPath.Source)" -ForegroundColor Green } else { $result.Issues += "Git is not installed or not in PATH" Write-Host " ✗ Git not found" -ForegroundColor Red @@ -92,7 +98,7 @@ try { } # Step 2: Verify PowerShell version - Write-Host "`n[2/6] Checking PowerShell version..." -ForegroundColor Cyan + Write-Host "`n[2/5] Checking PowerShell version..." -ForegroundColor Cyan $psVersion = $PSVersionTable.PSVersion if ($psVersion.Major -ge 5) { Write-Host " ✓ PowerShell $($psVersion.ToString()) (>= 5.0)" -ForegroundColor Green @@ -102,7 +108,7 @@ try { } # Step 3: Verify Visual Studio - Write-Host "`n[3/6] Checking Visual Studio installation..." -ForegroundColor Cyan + Write-Host "`n[3/5] Checking Visual Studio installation..." -ForegroundColor Cyan $msBuildPaths = @( "${env:ProgramFiles}\Microsoft Visual Studio\2022\*\MSBuild\Current\Bin\MSBuild.exe", "${env:ProgramFiles}\Microsoft Visual Studio\2019\*\MSBuild\Current\Bin\MSBuild.exe", @@ -127,73 +133,68 @@ try { } # Step 4: Verify repository remotes - Write-Host "`n[4/6] Checking repository remotes..." -ForegroundColor Cyan + Write-Host "`n[4/5] Checking repository remotes..." -ForegroundColor Cyan if ($result.GitInstalled) { - $remotes = git remote 2>&1 - $expectedRemotes = @('origin', 'upstream', 'upstream-pwsh') - $missingRemotes = @() - - foreach ($remote in $expectedRemotes) { - if ($remotes -contains $remote) { - $remoteUrl = git remote get-url $remote 2>&1 - Write-Host " ✓ $remote configured: $remoteUrl" -ForegroundColor Green + try { + $gitConfigPath = Join-Path $repoRoot ".git\config" + if (Test-Path $gitConfigPath) { + $gitConfig = Get-Content $gitConfigPath -Raw + $expectedRemotes = @('origin', 'upstream', 'upstream-pwsh') + $missingRemotes = @() + + foreach ($remote in $expectedRemotes) { + if ($gitConfig -match "\[remote `"$remote`"\]") { + Write-Host " ✓ $remote configured" -ForegroundColor Green + } else { + $missingRemotes += $remote + Write-Host " ✗ $remote not configured" -ForegroundColor Red + } + } + + if ($missingRemotes.Count -eq 0) { + $result.RemotesConfigured = $true + } else { + $result.Issues += "Missing remotes: $($missingRemotes -join ', ')" + } } else { - $missingRemotes += $remote - Write-Host " ✗ $remote not configured" -ForegroundColor Red + $result.Issues += "Not a git repository" + Write-Host " ✗ Not a git repository" -ForegroundColor Red } - } - - if ($missingRemotes.Count -eq 0) { - $result.RemotesConfigured = $true - } else { - $result.Issues += "Missing remotes: $($missingRemotes -join ', ')" + } catch { + $result.Issues += "Error checking remotes: $($_.Exception.Message)" + Write-Host " ✗ Error checking remotes" -ForegroundColor Red } } else { Write-Host " ⊘ Skipped (Git not available)" -ForegroundColor Yellow } # Step 5: Verify target version exists - Write-Host "`n[5/6] Checking target version exists..." -ForegroundColor Cyan + Write-Host "`n[5/5] Checking target version exists..." -ForegroundColor Cyan if ($result.GitInstalled -and $result.RemotesConfigured) { - Write-Host " Fetching from upstream..." -ForegroundColor Gray - git fetch upstream --tags 2>&1 | Out-Null - - # Check if it's a tag first, then try as a branch - $tagExists = git rev-parse "$TargetVersion" 2>&1 - if ($LASTEXITCODE -eq 0) { - $result.TargetExists = $true - Write-Host " ✓ Target tag exists: $TargetVersion" -ForegroundColor Green - } else { - # Try as a branch - $branchExists = git rev-parse "upstream/$TargetVersion" 2>&1 - if ($LASTEXITCODE -eq 0) { + try { + # Check if tag/branch ref exists in .git directory + $tagPath = Join-Path $repoRoot ".git\refs\tags\$TargetVersion" + $upstreamBranchPath = Join-Path $repoRoot ".git\refs\remotes\upstream\$TargetVersion" + + if (Test-Path $tagPath) { + $result.TargetExists = $true + Write-Host " ✓ Target tag exists locally: $TargetVersion" -ForegroundColor Green + } elseif (Test-Path $upstreamBranchPath) { $result.TargetExists = $true Write-Host " ✓ Target branch exists: upstream/$TargetVersion" -ForegroundColor Green } else { - $result.Issues += "Target version/tag '$TargetVersion' not found in upstream" - Write-Host " ✗ Target '$TargetVersion' not found" -ForegroundColor Red + $result.Issues += "Target version/tag '$TargetVersion' not found locally. Run 'git fetch upstream --tags' to update." + Write-Host " ✗ Target '$TargetVersion' not found locally" -ForegroundColor Red + Write-Host " Hint: Run 'git fetch upstream --tags' to fetch latest tags" -ForegroundColor Yellow } + } catch { + $result.Issues += "Error checking target version: $($_.Exception.Message)" + Write-Host " ✗ Error checking target version" -ForegroundColor Red } } else { Write-Host " ⊘ Skipped (prerequisites not met)" -ForegroundColor Yellow } - # Step 6: Verify working directory is clean - Write-Host "`n[6/6] Checking working directory state..." -ForegroundColor Cyan - if ($result.GitInstalled) { - $status = git status --porcelain 2>&1 - if ([string]::IsNullOrWhiteSpace($status)) { - $result.WorkingDirClean = $true - Write-Host " ✓ Working directory is clean" -ForegroundColor Green - } else { - $result.Issues += "Working directory has uncommitted changes" - Write-Host " ✗ Working directory has uncommitted changes" -ForegroundColor Red - Write-Host " Run 'git status' to see changes" -ForegroundColor Yellow - } - } else { - Write-Host " ⊘ Skipped (Git not available)" -ForegroundColor Yellow - } - # Final evaluation Write-Host "`n========================================" -ForegroundColor Cyan Write-Host "PREREQUISITE CHECK SUMMARY" -ForegroundColor Cyan @@ -203,8 +204,7 @@ try { $result.GitInstalled, $result.VSInstalled, $result.RemotesConfigured, - $result.TargetExists, - $result.WorkingDirClean + $result.TargetExists ) $result.Success = ($criticalChecks | Where-Object { $_ -eq $false }).Count -eq 0 @@ -229,6 +229,7 @@ try { Write-Host "" + # Return result return $result } catch { @@ -242,7 +243,12 @@ try { $result.Issues += "Tool error: $($_.Exception.Message)" $result.Message = "Prerequisite check failed with error: $($_.Exception.Message)" + # Return result return $result } finally { - Pop-Location + try { + Pop-Location + } catch { + # Ignore Pop-Location errors in MCP context + } } From 09f0fd65a737bb86fa6e203eb84decbf37e44f66 Mon Sep 17 00:00:00 2001 From: User Date: Thu, 15 Jan 2026 08:14:26 -0800 Subject: [PATCH 44/68] update instructions to use mcp tool to read build logs --- .github/instructions/build.instructions.md | 16 +++++++++++++--- .../merge/merge-process-overview.instructions.md | 9 ++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md index 84b10974f4c0..52621f426530 100644 --- a/.github/instructions/build.instructions.md +++ b/.github/instructions/build.instructions.md @@ -149,12 +149,22 @@ fatal error C1083: Cannot open source file: 'newfile.c' 5. **Rebuild and verify** using Build-OpenSSH MCP tool again 6. **Commit fixes with detailed message** -#### Manual Error Analysis Commands (if needed): -Parse errors manually from log file using the Test-OpenSSHBuild MCP tool: +#### Reading Build Logs and Errors: +**ALWAYS use the Test-OpenSSHBuild MCP tool to read build logs and parse errors:** - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` -Or use direct MSBuild with detailed logging: +**Why use this tool:** +- Automatically locates and parses the build log file +- Provides structured error output with file paths, line numbers, error codes, and messages +- Groups errors and warnings for easier analysis +- Works reliably in MCP context where direct file reading may not be available + +**DO NOT** attempt to read log files directly with `Get-Content` or similar commands. +**DO NOT** try to locate log files manually. + +#### Alternative: Direct MSBuild (Terminal Only) +Only use this when running directly in a terminal (not via MCP): ```pwsh & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" .\contrib\win32\openssh\Win32-OpenSSH.sln /p:Configuration=Release /p:Platform=x64 /v:detailed ``` diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index f9899a1cd993..07bbf4d42adb 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -158,7 +158,14 @@ The process consists of several interconnected phases: - **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` - If build fails, fix issues and rebuild. Commit any build fixes separately. + If build fails: + - **Use Test-OpenSSHBuild MCP tool to read the build log and parse errors**: + - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHBuild` + - **Parameters**: `Configuration="Release"`, `Architecture="x64"` + - **DO NOT** try to read log files directly with `Get-Content` or locate them manually + - Fix issues based on parsed error output + - Rebuild and verify + - Commit any build fixes separately with descriptive messages 10. **Validate if batch ended with successful CI:** Check the end commit's CI status from Get-CommitGroups output. From 59178e8144589dffc9d8a92c22aef18cb146895a Mon Sep 17 00:00:00 2001 From: User Date: Thu, 15 Jan 2026 08:38:26 -0800 Subject: [PATCH 45/68] update build verfication script to check correct location for artifacts --- .github/tools/Test-OpenSSHBuild.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/tools/Test-OpenSSHBuild.ps1 b/.github/tools/Test-OpenSSHBuild.ps1 index afb978b778bc..a1a8c3389682 100644 --- a/.github/tools/Test-OpenSSHBuild.ps1 +++ b/.github/tools/Test-OpenSSHBuild.ps1 @@ -47,7 +47,7 @@ Tests debug build artifacts for x86 using a custom log file location. .NOTES - - Expected build artifact location: contrib\win32\openssh\{Architecture}\{Configuration}\ + - Expected build artifact location: bin\{Architecture}\{Configuration}\ - Error parsing regex: ^(?.*?)\((?\d+)[,)].*?error (?(C|LNK)\d+): (?.*)$ - Warning parsing regex: ^(?.*?)\((?\d+)[,)].*?warning (?(C|LNK)\d+): (?.*)$ #> @@ -96,7 +96,7 @@ try { Write-Host "========================================`n" -ForegroundColor Cyan # Define build output path - $buildPath = Join-Path $repoRoot "contrib\win32\openssh\$Architecture\$Configuration" + $buildPath = Join-Path $repoRoot "bin\$Architecture\$Configuration" if (-not (Test-Path $buildPath)) { Write-Host "Build path does not exist: $buildPath" -ForegroundColor Red From 298557951af0388caafaebf87e1e742383c27479 Mon Sep 17 00:00:00 2001 From: User Date: Thu, 15 Jan 2026 09:04:29 -0800 Subject: [PATCH 46/68] fix typo --- .github/tools/Get-CommitGroups.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/tools/Get-CommitGroups.ps1 b/.github/tools/Get-CommitGroups.ps1 index be7677800325..0c5411dcedb3 100644 --- a/.github/tools/Get-CommitGroups.ps1 +++ b/.github/tools/Get-CommitGroups.ps1 @@ -280,7 +280,7 @@ try { # Process commits and check CI status $statusCheckMessage = if ($GroupByCIPresence) { "Checking CI presence for each commit..." } else { "Checking CI status for each commit..." } -Write-Host "\n$statusCheckMessage" -ForegroundColor Cyan +Write-Host "`n$statusCheckMessage" -ForegroundColor Cyan $commitsWithStatus = @() $chunks = @() From 76775b2b9c68618868c37af644393f3cc1d81f06 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Fri, 23 Jan 2026 12:27:46 -0500 Subject: [PATCH 47/68] fix tool to parse build log for success/failure and update instructions --- .github/agents/merge-upstream.agent.md | 44 +-- .github/instructions/build.instructions.md | 80 +++--- .../merge/merge-details.instructions.md | 12 +- .../merge-process-overview.instructions.md | 48 ++-- .github/instructions/testing.instructions.md | 6 +- .github/tools/Build-OpenSSH.ps1 | 254 ------------------ .github/tools/Start-OpenSSHBuild.ps1 | 31 ++- .github/tools/Test-MergePrerequisites.ps1 | 12 +- .github/tools/Test-OpenSSHBuild.ps1 | 86 +++++- 9 files changed, 194 insertions(+), 379 deletions(-) delete mode 100644 .github/tools/Build-OpenSSH.ps1 diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index a45f950eb2cf..36b8695b4809 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -35,16 +35,16 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - **Usage**: Use the MCP tool function directly - it handles all GitHub API calls - **If tool unavailable**: ERROR - This tool is required for the merge workflow -2. **Build-OpenSSH MCP Tool** - Build automation and verification - - **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` - - **Parameters**: - - `Configuration` (string, optional): Build configuration - "Debug" or "Release" (default: "Release") - - `Architecture` (string, optional): Target architecture - "x64", "x86", "ARM", "ARM64" (default: "x64") - - `Clean` (boolean, optional): Perform clean build (default: false) - - **If tool unavailable**: ERROR - This tool is required for the merge workflow +2. **Start-OpenSSHBuild MCP Tool** - Build automation + - **MCP Tool Name**: `mcp_openssh-server_Start_OpenSSHBuild` + - **Parameters**: + - `Configuration` (string, optional): Build configuration - "Debug" or "Release" (default: "Release") + - `Architecture` (string, optional): Target architecture - "x64", "x86", "ARM", "ARM64" (default: "x64") + - `Clean` (boolean, optional): Perform clean build (default: false) + - **If tool unavailable**: ERROR - This tool is required for the merge workflow 3. **Test-OpenSSHFunctionality MCP Tool** - Functional testing - - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - **Parameters**: - `Configuration` (string, optional): Build configuration - "Debug" or "Release" (default: "Release") - `Architecture` (string, optional): Target architecture - "x64", "x86", "ARM", "ARM64" (default: "x64") @@ -94,7 +94,7 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - **Parameters**: - `TargetVersion` (string, required): Upstream version/tag to merge (e.g., "V_10_0_P2") - `SkipBaselineBuild` (boolean, optional): Skip baseline build check (default: false) - + This single tool verifies: - Git, PowerShell, Visual Studio are available - Repository remotes are configured (origin, upstream, upstream-pwsh) @@ -124,11 +124,11 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for **Steps:** 1. **Get first commit batch** using Get-CommitGroups MCP tool: - - **MCP Tool Name**: `mcp_openssh-serve_Get_CommitGroups` + - **MCP Tool Name**: `mcp_openssh-server_Get_CommitGroups` - **Parameters**: - For first batch: `GitHubTag="V_10_0_P2"`, `FirstChunkOnly=true`, `GroupByCIPresence=true` - For subsequent batches: `StartCommit=""`, `FirstChunkOnly=true`, `GroupByCIPresence=true` - + **The tool returns structured data:** ```json { @@ -170,7 +170,7 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for **Steps:** 1. **Build the merged code:** - - **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` + - **MCP Tool Name**: `mcp_openssh-server_Start_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` 2. **If build fails, fix compilation errors:** @@ -185,9 +185,9 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - Look for commits ending with `CIStatus: "success"` in the detailed output 4. **If CI was successful, run validation tests:** - - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - **Parameters**: (use defaults for Release/x64) - + This test installs service, creates test user, validates SSH connectivity. If tests fail, fix issues and commit fixes. **Do not proceed** to next batch until tests pass. @@ -216,31 +216,31 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for 1. **Generate batch summary:** ```markdown ## Batch Completion Summary - + **Batch:** [StartCommit]..[EndCommit] ( commits) **End Commit CI Status:** - + ### Changes in this Batch: - - - - + ### Conflicts Resolved: - : - : - + ### Build Status: - Build: ✅ Success / ❌ Failed - Build fixes applied: - + ### Validation Status: - Validation tests: ✅ Passed / ⏭️ Skipped (no successful CI) / ❌ Failed - Test fixes applied: - + ### Next Steps: - Next batch will start from commit: - Estimated remaining batches: - + **Ready to proceed to next batch?** ``` @@ -266,7 +266,7 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - Summarize and get approval 3. **Continue** until all target commits are merged or HEAD is reached 4. **Perform final comprehensive validation:** - - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - **Parameters**: (use defaults for Release/x64) **Success Criteria:** diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md index 52621f426530..0dca61bd3fb9 100644 --- a/.github/instructions/build.instructions.md +++ b/.github/instructions/build.instructions.md @@ -28,31 +28,11 @@ git --version ### Using MCP Build Tools (Recommended) -The repository includes MCP tools that automate the build, verification, and error analysis process. +The repository includes MCP tools that automate the build and error analysis process. -#### Option 1: Build & Verify (Recommended) -Use the Build-OpenSSH MCP tool: -- **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` -- **Parameters**: - - `Configuration` (optional): "Debug" or "Release" (default: "Release") - - `Architecture` (optional): "x64", "x86", "ARM", "ARM64" (default: "x64") - - `Clean` (optional): Perform clean build (default: false) - -**Examples:** -- Complete build and verification: `Configuration="Release"`, `Architecture="x64"` -- Clean build: `Configuration="Release"`, `Architecture="x64"`, `Clean=true` -- Debug build: `Configuration="Debug"`, `Architecture="x64"` - -**Output includes:** -- Build success/failure status -- All 14 expected artifacts verification -- Parsed compilation errors with file/line/code/message -- Build warnings summary -- Build log location - -#### Option 2: Build Only +#### Build Use the Start-OpenSSHBuild MCP tool: -- **MCP Tool Name**: `mcp_openssh-serve_Start_OpenSSHBuild` +- **MCP Tool Name**: `mcp_openssh-server_Start_OpenSSHBuild` - **Parameters**: - `Configuration` (optional): "Debug" or "Release" (default: "Release") - `Architecture` (optional): "x64", "x86", "ARM", "ARM64" (default: "x64") @@ -62,9 +42,9 @@ Use the Start-OpenSSHBuild MCP tool: - Incremental build: `Configuration="Release"`, `Architecture="x64"` - Clean build: `Configuration="Release"`, `Architecture="x64"`, `Clean=true` -#### Option 3: Test Existing Build -Use the Test-OpenSSHBuild MCP tool: -- **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHBuild` +#### Test Existing Build (on failure only) +Use the Test-OpenSSHBuild MCP tool when a build fails: +- **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` ## Compilation Error Resolution @@ -140,29 +120,34 @@ fatal error C1083: Cannot open source file: 'newfile.c' ### Step-by-Step Error Resolution #### AI Agent Workflow: -1. **Attempt initial build using MCP tool** - - **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` - - **Parameters**: `Configuration="Release"`, `Architecture="x64"` -2. **Review structured error output** from Build-OpenSSH tool -3. **Categorize error types** (preprocessor, Windows compatibility, build system) -4. **Apply appropriate resolution strategy** (see error categories above) -5. **Rebuild and verify** using Build-OpenSSH MCP tool again -6. **Commit fixes with detailed message** - -#### Reading Build Logs and Errors: -**ALWAYS use the Test-OpenSSHBuild MCP tool to read build logs and parse errors:** -- **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHBuild` +1. **Run the build** + - Use: **Start-OpenSSHBuild** — `mcp_openssh-server_Start_OpenSSHBuild` with `Configuration` and `Architecture`. +2. **If build succeeded**, skip log parsing and proceed. +3. **If build failed**, **parse errors** using **Test-OpenSSHBuild** — `mcp_openssh-server_Test_OpenSSHBuild`. +4. **Categorize error types** (preprocessor, Windows compatibility, build system). +5. **Apply appropriate resolution strategy** (see error categories above) and **rebuild** with Start-OpenSSHBuild. +6. **Commit fixes with detailed message**. + +#### Reading Build Logs and Errors (on failure only) +Use the **Test-OpenSSHBuild** MCP tool to read build logs and parse errors **only when the build fails**: +- **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` **Why use this tool:** - Automatically locates and parses the build log file -- Provides structured error output with file paths, line numbers, error codes, and messages +- Provides structured error output with file paths, codes, and messages - Groups errors and warnings for easier analysis - Works reliably in MCP context where direct file reading may not be available **DO NOT** attempt to read log files directly with `Get-Content` or similar commands. **DO NOT** try to locate log files manually. +### Build Tools Invocation Policy + +- Use `Start-OpenSSHBuild.ps1` to run the build for each chunk/batch. +- Only if `Start-OpenSSHBuild.ps1` reports the build failed, invoke `Test-OpenSSHBuild.ps1` to parse errors and warnings from the build log. +- Skip `Test-OpenSSHBuild.ps1` when the build succeeded to avoid unnecessary log parsing. + #### Alternative: Direct MSBuild (Terminal Only) Only use this when running directly in a terminal (not via MCP): ```pwsh @@ -228,19 +213,18 @@ contrib\win32\openssh\ ### Build Verification Using MCP Tools ```pwsh -Use the Build-OpenSSH MCP tool (recommended): -- **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` +Use the Start-OpenSSHBuild MCP tool (recommended): +- **MCP Tool Name**: `mcp_openssh-server_Start_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` -Or test an existing build: -- **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHBuild` -- **Parameters**: `Configuration="Release"`, `Architecture="x64" -**Expected Output:** -- Success/failure status -- 14 of 14 artifacts found +If the build fails, parse errors with: +- **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHBuild` +- **Parameters**: `Configuration="Release"`, `Architecture="x64"` +**Expected Output (on failure):** +- Build failure status - Parsed errors and warnings - Build log location - +``` **Expected Artifacts (14 executables):** - ssh.exe, sshd.exe, sshd-auth.exe, sshd-session.exe - ssh-agent.exe, ssh-add.exe, ssh-keygen.exe, ssh-keyscan.exe diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md index 5c6f20d909d4..a2358a04cb2b 100644 --- a/.github/instructions/merge/merge-details.instructions.md +++ b/.github/instructions/merge/merge-details.instructions.md @@ -253,12 +253,14 @@ FUNCTION automated_build_fix(): iteration = 0 WHILE iteration < MAX_ITERATIONS: - build_result = attempt_build() + // Always start with Start-OpenSSHBuild.ps1 + build_result = start_openssh_build(Configuration="Release", Architecture="x64") IF build_result.success: RETURN SUCCESS - errors = parse_build_errors(build_result.output) + // Only invoke Test-OpenSSHBuild.ps1 when build failed + errors = test_openssh_build(Configuration="Release", Architecture="x64", LogFile=build_result.log) fixes_applied = [] FOR EACH error IN errors: @@ -289,6 +291,12 @@ FUNCTION determine_fix_strategy(error): RETURN null ``` +### Build Tools Invocation Policy + +- Use `Start-OpenSSHBuild.ps1` to run the build for each chunk/batch. +- Only if `Start-OpenSSHBuild.ps1` reports the build failed, invoke `Test-OpenSSHBuild.ps1` to parse errors and warnings from the build log. +- Skip `Test-OpenSSHBuild.ps1` when the build succeeded to avoid unnecessary log parsing. + ## Testing Automation Framework ### Automated Test Execution diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 07bbf4d42adb..857ffcc7aa3f 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -53,14 +53,14 @@ The process consists of several interconnected phases: # Run prerequisite check via MCP tool # MCP Tool Name: mcp_openssh-server_Test_MergePrerequisites # Parameters: TargetVersion (required), SkipBaselineBuild (optional) - + # Example invocation (replace with target like "V_10_0_P2"): # The MCP tool will verify: # - Git, PowerShell, Visual Studio installed # - Repository remotes configured (origin, upstream, upstream-pwsh) # - Target version exists in upstream # - Working directory is clean - + # Proceed only if tool reports "ALL PREREQUISITES MET" ``` @@ -68,7 +68,7 @@ The process consists of several interconnected phases: ```pwsh git config core.editor true ``` - + 3. **Create merge branch from current branch:** ```pwsh git checkout -b merge-v- @@ -78,20 +78,20 @@ The process consists of several interconnected phases: ### Perform Merge with Grouped Commits 4. **Identify merge range and group commits:** Use the Get-CommitGroups MCP tool with `-FirstChunkOnly -GroupByCIPresence` - - **MCP Tool Name**: `mcp_openssh-serve_Get_CommitGroups` - + + **MCP Tool Name**: `mcp_openssh-server_Get_CommitGroups` + **Parameters**: - `GitHubTag` (string, optional): Start from last merged tag (e.g., "V_10_0_P2") - `StartCommit` (string, optional): Start from specific commit SHA - `FirstChunkOnly` (boolean): Set to `true` - `GroupByCIPresence` (boolean): Set to `true` - + **Example for first batch**: - Find the last upstream tag in the fork - Call tool with `GitHubTag=`, `FirstChunkOnly=true`, `GroupByCIPresence=true` - This gets commits ending with any commit that has CI runs (not just successful CI) - + **Example output:** ```json { @@ -129,9 +129,9 @@ The process consists of several interconnected phases: Write-Host "Cherry-picking commit: $commit" git cherry-pick $commit - # Build after every commit so we can attribute failures precisely. - # Use MCP Tool: mcp_openssh-serve_Build_OpenSSH - # Parameters: Configuration="Release", Architecture="x64" + # Build after every commit to attribute failures precisely. + # Use MCP Tool: mcp_openssh-server_Start_OpenSSHBuild + # Parameters: Configuration="Release", Architecture="x64" } ``` @@ -154,25 +154,25 @@ The process consists of several interconnected phases: ``` 9. **Build after completing the batch:** - Use the Build-OpenSSH MCP tool: - - **MCP Tool Name**: `mcp_openssh-serve_Build_OpenSSH` - - **Parameters**: `Configuration="Release"`, `Architecture="x64"` - - If build fails: - - **Use Test-OpenSSHBuild MCP tool to read the build log and parse errors**: - - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHBuild` + Use the Start-OpenSSHBuild MCP tool: + - **MCP Tool Name**: `mcp_openssh-server_Start_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` - - **DO NOT** try to read log files directly with `Get-Content` or locate them manually - - Fix issues based on parsed error output - - Rebuild and verify - - Commit any build fixes separately with descriptive messages + + If build fails: + - **Use Test-OpenSSHBuild MCP tool to read the build log and parse errors**: + - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHBuild` + - **Parameters**: `Configuration="Release"`, `Architecture="x64"` + - **DO NOT** try to read log files directly with `Get-Content` or locate them manually + - Fix issues based on parsed error output + - Rebuild and verify + - Commit any build fixes separately with descriptive messages 10. **Validate if batch ended with successful CI:** Check the end commit's CI status from Get-CommitGroups output. If CI was successful, run validation: - - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - **Parameters**: (use defaults for Release/x64) - + If no CI or failed CI, skip validation (build success is sufficient). 11. **Provide summary and get approval:** diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index 00a60db8fc4d..c4f675b3a83c 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -14,7 +14,7 @@ This document provides comprehensive testing procedures for validating OpenSSH-P The repository includes an MCP tool that automates end-to-end functional testing of OpenSSH on Windows. Use the Test-OpenSSHFunctionality MCP tool: -- **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` +- **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - **Parameters**: - `Configuration` (optional): "Debug" or "Release" (default: "Release") - `Architecture` (optional): "x64", "x86", "ARM", "ARM64" (default: "x64") @@ -84,7 +84,7 @@ The MCP tool performs comprehensive end-to-end testing including: - Command execution verification - Complete cleanup of all test resources -**MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` +**MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` **Parameters**: - `Configuration` (optional): "Debug" or "Release" (default: "Release") @@ -245,7 +245,7 @@ Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*SSH*"} ### Recommended Testing Workflow for AI Agents 1. **After successful build**, run the automated functionality test: - - **MCP Tool Name**: `mcp_openssh-serve_Test_OpenSSHFunctionality` + - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - **Parameters**: (use defaults) 2. **If test passes**, the merge is validated for basic SSH functionality diff --git a/.github/tools/Build-OpenSSH.ps1 b/.github/tools/Build-OpenSSH.ps1 deleted file mode 100644 index 692559066f0e..000000000000 --- a/.github/tools/Build-OpenSSH.ps1 +++ /dev/null @@ -1,254 +0,0 @@ -<# -.SYNOPSIS - MCP wrapper tool that builds and verifies OpenSSH in a single operation. - -.DESCRIPTION - This script combines Start-OpenSSHBuild and Test-OpenSSHBuild into a single - convenient operation. It first builds OpenSSH using the specified configuration - and architecture, then tests that all expected artifacts were produced and - parses the build log for errors. - - This is the recommended tool for most build operations as it provides - comprehensive feedback in one step. - -.PARAMETER Configuration - Build configuration type. Valid values: 'Debug', 'Release' - Default: 'Release' - -.PARAMETER Architecture - Target architecture for the build. Valid values: 'x64', 'x86', 'ARM', 'ARM64' - Default: 'x64' - -.PARAMETER Clean - When specified, performs a clean build by deleting existing build artifacts first. - Default: false (incremental build) - -.PARAMETER NoOpenSSL - Build without OpenSSL support. - Default: false - -.PARAMETER OneCore - Build for Windows OneCore API subset. - Default: false - -.OUTPUTS - Returns a consolidated hashtable with: - - BuildSuccess: Boolean indicating if build succeeded - - TestSuccess: Boolean indicating if test passed - - OverallSuccess: Boolean indicating both build and test succeeded - - BuildExitCode: MSBuild exit code - - BuildMessage: Build status message - - TotalArtifacts: Count of artifacts found - - ExpectedArtifacts: Count of artifacts expected (14) - - ArtifactsMissing: Array of missing artifacts - - Errors: Array of parsed error objects - - Warnings: Array of parsed warning objects - - LogFile: Path to build log file - - BuildPath: Path to build output directory - - Message: Overall summary message - -.EXAMPLE - .\Build-OpenSSH.ps1 -Configuration Release -Architecture x64 - - Performs an incremental release build for x64 and tests artifacts. - -.EXAMPLE - .\Build-OpenSSH.ps1 -Configuration Debug -Architecture x64 -Clean - - Performs a clean debug build for x64 and tests artifacts. - -.EXAMPLE - .\Build-OpenSSH.ps1 -Architecture ARM64 -OneCore - - Performs an incremental OneCore release build for ARM64 and tests artifacts. - -.NOTES - - This tool calls Start-OpenSSHBuild.ps1 and Test-OpenSSHBuild.ps1 - - Requires Visual Studio 2019 or later with C++ tools - - Requires Windows SDK 10.0.17763.0 or later - - Build artifacts output to: contrib\win32\openssh\{Architecture}\{Configuration}\ - - Build log written to: OpenSSH{Configuration}{Architecture}.log -#> - -param( - [Parameter(Mandatory=$false)] - [ValidateSet('Debug', 'Release')] - [string]$Configuration = 'Release', - - [Parameter(Mandatory=$false)] - [ValidateSet('x64', 'x86', 'ARM', 'ARM64')] - [string]$Architecture = 'x64', - - [Parameter(Mandatory=$false)] - [switch]$Clean, - - [Parameter(Mandatory=$false)] - [switch]$NoOpenSSL, - - [Parameter(Mandatory=$false)] - [switch]$OneCore -) - -$scriptRoot = $PSScriptRoot - -try { - Write-Host "========================================" -ForegroundColor Cyan - Write-Host "OpenSSH Build & Test Tool (MCP)" -ForegroundColor Cyan - Write-Host "========================================" -ForegroundColor Cyan - Write-Host "Configuration: $Configuration" -ForegroundColor White - Write-Host "Architecture: $Architecture" -ForegroundColor White - Write-Host "Clean Build: $Clean" -ForegroundColor White - Write-Host "No OpenSSL: $NoOpenSSL" -ForegroundColor White - Write-Host "OneCore: $OneCore" -ForegroundColor White - Write-Host "========================================`n" -ForegroundColor Cyan - - # Step 1: Build - Write-Host "STEP 1: Building OpenSSH..." -ForegroundColor Cyan - Write-Host "----------------------------------------`n" -ForegroundColor Cyan - - $buildParams = @{ - Configuration = $Configuration - Architecture = $Architecture - } - - if ($Clean) { - $buildParams['Clean'] = $true - } - - if ($NoOpenSSL) { - $buildParams['NoOpenSSL'] = $true - } - - if ($OneCore) { - $buildParams['OneCore'] = $true - } - - $buildScriptPath = Join-Path $scriptRoot "Start-OpenSSHBuild.ps1" - $buildResult = & $buildScriptPath @buildParams - - if (-not $buildResult.Success) { - Write-Host "`n⚠ Build failed, skipping test" -ForegroundColor Yellow - - # Return build result with test skipped - $result = @{ - BuildSuccess = $false - TestSuccess = $false - OverallSuccess = $false - BuildExitCode = $buildResult.ExitCode - BuildMessage = $buildResult.Message - TotalArtifacts = 0 - ExpectedArtifacts = 14 - ArtifactsMissing = @() - Errors = @() - Warnings = @() - LogFile = $buildResult.LogFile - BuildPath = $buildResult.BuildPath - Message = "Build failed: $($buildResult.Message)" - } - - return $result - } - - # Step 2: Test - Write-Host "`nSTEP 2: Testing Build Artifacts..." -ForegroundColor Cyan - Write-Host "----------------------------------------`n" -ForegroundColor Cyan - - $testParams = @{ - Configuration = $Configuration - Architecture = $Architecture - LogFile = $buildResult.LogFile - } - - $testScriptPath = Join-Path $scriptRoot "Test-OpenSSHBuild.ps1" - $testResult = & $testScriptPath @testParams - - # Step 3: Consolidate results - $overallSuccess = $buildResult.Success -and $testResult.Success - - Write-Host "`n========================================" -ForegroundColor $(if ($overallSuccess) { "Green" } else { "Red" }) - Write-Host "OVERALL RESULT: $(if ($overallSuccess) { 'SUCCESS' } else { 'FAILED' })" -ForegroundColor $(if ($overallSuccess) { "Green" } else { "Red" }) - Write-Host "========================================" -ForegroundColor $(if ($overallSuccess) { "Green" } else { "Red" }) - - if ($overallSuccess) { - Write-Host "✓ Build succeeded" -ForegroundColor Green - Write-Host "✓ All $($testResult.ExpectedArtifacts) artifacts tested" -ForegroundColor Green - Write-Host "✓ No errors found" -ForegroundColor Green - } else { - if (-not $buildResult.Success) { - Write-Host "✗ Build failed with exit code $($buildResult.ExitCode)" -ForegroundColor Red - } - if ($testResult.ArtifactsMissing.Count -gt 0) { - Write-Host "✗ $($testResult.ArtifactsMissing.Count) artifacts missing" -ForegroundColor Red - } - if ($testResult.Errors.Count -gt 0) { - Write-Host "✗ $($testResult.Errors.Count) error(s) found" -ForegroundColor Red - } - } - - Write-Host "`nBuild artifacts: $($buildResult.BuildPath)" -ForegroundColor White - Write-Host "Build log: $($buildResult.LogFile)" -ForegroundColor White - - # Build consolidated message - $messageParts = @() - if ($buildResult.Success) { - $messageParts += "Build succeeded" - } else { - $messageParts += "Build failed" - } - - if ($testResult.Success) { - $messageParts += "all artifacts tested" - } else { - if ($testResult.ArtifactsMissing.Count -gt 0) { - $messageParts += "$($testResult.ArtifactsMissing.Count) artifacts missing" - } - if ($testResult.Errors.Count -gt 0) { - $messageParts += "$($testResult.Errors.Count) errors" - } - } - - $consolidatedMessage = $messageParts -join ", " - - $result = @{ - BuildSuccess = $buildResult.Success - TestSuccess = $testResult.Success - OverallSuccess = $overallSuccess - BuildExitCode = $buildResult.ExitCode - BuildMessage = $buildResult.Message - TotalArtifacts = $testResult.TotalArtifacts - ExpectedArtifacts = $testResult.ExpectedArtifacts - ArtifactsMissing = $testResult.ArtifactsMissing - Errors = $testResult.Errors - Warnings = $testResult.Warnings - LogFile = $buildResult.LogFile - BuildPath = $buildResult.BuildPath - Message = $consolidatedMessage - } - - return $result - -} catch { - Write-Host "`n========================================" -ForegroundColor Red - Write-Host "ERROR" -ForegroundColor Red - Write-Host "========================================" -ForegroundColor Red - Write-Host $_.Exception.Message -ForegroundColor Red - Write-Host $_.ScriptStackTrace -ForegroundColor Gray - - $result = @{ - BuildSuccess = $false - TestSuccess = $false - OverallSuccess = $false - BuildExitCode = -1 - BuildMessage = "Tool error: $($_.Exception.Message)" - TotalArtifacts = 0 - ExpectedArtifacts = 14 - ArtifactsMissing = @() - Errors = @() - Warnings = @() - LogFile = "" - BuildPath = "" - Message = "Build & test tool error: $($_.Exception.Message)" - } - - return $result -} diff --git a/.github/tools/Start-OpenSSHBuild.ps1 b/.github/tools/Start-OpenSSHBuild.ps1 index c175b29c6394..b6b0c6e2c3d0 100644 --- a/.github/tools/Start-OpenSSHBuild.ps1 +++ b/.github/tools/Start-OpenSSHBuild.ps1 @@ -116,8 +116,8 @@ try { Write-Host "✓ Cleaned: $buildPath" -ForegroundColor Green } - # Build log file path - $logFile = Join-Path $repoRoot "OpenSSH$Configuration$Architecture.log" + # Build log file path (align with other tools under contrib\win32\openssh) + $logFile = Join-Path $repoRoot "contrib\win32\openssh\OpenSSH$Configuration$Architecture.log" Write-Host "`nBuild log: $logFile" -ForegroundColor Gray # Prepare parameters for Start-OpenSSHBuild @@ -140,8 +140,25 @@ try { $buildResult = Start-OpenSSHBuild @buildParams - # Check build result - if ($buildResult -eq 0) { + # Determine build success primarily via log markers, not exit code + $successByLog = $null + $buildLogTime = $null + if (Test-Path $logFile) { + $buildLogTime = (Get-Item $logFile -ErrorAction SilentlyContinue).LastWriteTime + $logLines = Get-Content $logFile -ErrorAction SilentlyContinue + if ($logLines) { + $lastMarker = $null + foreach ($line in $logLines) { + if ($line -match '(?i)Build\s+(FAILED|Failed)') { $lastMarker = 'FAILED' } + elseif ($line -match '(?i)Build\s+(SUCCEEDED|Succeeded)') { $lastMarker = 'SUCCEEDED' } + } + if ($lastMarker) { $successByLog = ($lastMarker -eq 'SUCCEEDED') } + } + } + + $derivedSuccess = if ($successByLog -ne $null) { $successByLog } else { $buildResult -eq 0 } + + if ($derivedSuccess) { Write-Host "`n========================================" -ForegroundColor Green Write-Host "BUILD SUCCEEDED" -ForegroundColor Green Write-Host "========================================" -ForegroundColor Green @@ -149,9 +166,10 @@ try { $result = @{ Success = $true - ExitCode = 0 + ExitCode = $buildResult LogFile = $logFile BuildPath = $buildPath + BuildLogTimestamp = $buildLogTime Message = "Build completed successfully" } } else { @@ -166,7 +184,8 @@ try { ExitCode = $buildResult LogFile = $logFile BuildPath = $buildPath - Message = "Build failed with exit code $buildResult. Check log file for details." + BuildLogTimestamp = $buildLogTime + Message = "Build failed (determined from log markers or exit code). Check log file for details." } } diff --git a/.github/tools/Test-MergePrerequisites.ps1 b/.github/tools/Test-MergePrerequisites.ps1 index 879ef45dc424..bd807746fa40 100644 --- a/.github/tools/Test-MergePrerequisites.ps1 +++ b/.github/tools/Test-MergePrerequisites.ps1 @@ -8,13 +8,13 @@ - Required tools (Git, PowerShell, Visual Studio) - Repository configuration (remotes, branch state) - Target version identification - + This tool should be run before starting Phase 1 of the merge workflow to ensure all prerequisites are met. It prevents wasted effort by catching configuration issues early. - + To verify baseline build capability, use the Test-OpenSSHBuild MCP tool: - mcp_openssh-serve_Test_OpenSSHBuild + mcp_openssh-server_Test_OpenSSHBuild .PARAMETER TargetVersion The upstream version/tag to merge (e.g., "V_10_0_P2", "V_9_9_P1") @@ -41,7 +41,7 @@ .NOTES - This is a Phase 1 Pre-Merge Setup verification tool - Should be run before creating the merge branch - - For baseline build verification, use: mcp_openssh-serve_Test_OpenSSHBuild + - For baseline build verification, use: mcp_openssh-server_Test_OpenSSHBuild - Part of the OpenSSH upstream merge workflow automation #> @@ -175,7 +175,7 @@ try { # Check if tag/branch ref exists in .git directory $tagPath = Join-Path $repoRoot ".git\refs\tags\$TargetVersion" $upstreamBranchPath = Join-Path $repoRoot ".git\refs\remotes\upstream\$TargetVersion" - + if (Test-Path $tagPath) { $result.TargetExists = $true Write-Host " ✓ Target tag exists locally: $TargetVersion" -ForegroundColor Green @@ -216,7 +216,7 @@ try { Write-Host " 1. Create merge branch: git checkout -b merge-$TargetVersion-$(Get-Date -Format 'yyyyMMdd')" -ForegroundColor Gray Write-Host " 2. Use Get-CommitGroups tool to identify first batch of commits" -ForegroundColor Gray Write-Host " 3. Begin cherry-picking commits from first batch" -ForegroundColor Gray - + $result.Message = "All prerequisites met. Ready to start merge." } else { Write-Host "✗ PREREQUISITES NOT MET" -ForegroundColor Red diff --git a/.github/tools/Test-OpenSSHBuild.ps1 b/.github/tools/Test-OpenSSHBuild.ps1 index a1a8c3389682..db50930c7d2b 100644 --- a/.github/tools/Test-OpenSSHBuild.ps1 +++ b/.github/tools/Test-OpenSSHBuild.ps1 @@ -48,8 +48,10 @@ .NOTES - Expected build artifact location: bin\{Architecture}\{Configuration}\ - - Error parsing regex: ^(?.*?)\((?\d+)[,)].*?error (?(C|LNK)\d+): (?.*)$ - - Warning parsing regex: ^(?.*?)\((?\d+)[,)].*?warning (?(C|LNK)\d+): (?.*)$ + - Error parsing regex (file/line form): ^(?.+?)\((?\d+)[,)].*?\s(?fatal error|error)\s+(?(?:C|LNK|MSB)\d+)\s*:\s*(?.+)$ + - Error parsing regex (generic/no line): ^(?:.*?:\s*)?(?fatal error|error)\s+(?(?:C|LNK|MSB)\d+)\s*:\s*(?.+)$ + - Warning parsing regex (file/line form): ^(?.+?)\((?\d+)[,)].*?\swarning\s+(?(?:C|LNK|MSB)\d+)\s*:\s*(?.+)$ + - Warning parsing regex (generic/no line): ^(?:.*?:\s*)?warning\s+(?(?:C|LNK|MSB)\d+)\s*:\s*(?.+)$ #> param( @@ -149,18 +151,38 @@ try { Write-Host "`nParsing build log: $LogFile" -ForegroundColor Cyan $logContent = Get-Content $LogFile -ErrorAction SilentlyContinue + $logTime = (Get-Item $LogFile -ErrorAction SilentlyContinue).LastWriteTime if ($logContent) { - # Error regex: file(line) : error CODE: message - # Example: c:\path\file.c(123): error C2065: 'identifier' : undeclared identifier - $errorRegex = '^(?.*?)\((?\d+)[,)].*?error (?(C|LNK)\d+): (?.*)$' + # Find the first build status marker and, if failed, only scan lines after it + $buildStatusIndex = $null + $buildFailed = $false + for ($i = 0; $i -lt $logContent.Count; $i++) { + $line = $logContent[$i] + if ($line -match 'Build\s+(FAILED|Failed)') { + $buildStatusIndex = $i + $buildFailed = $true + break + } elseif ($line -match 'Build\s+(SUCCEEDED|Succeeded)') { + $buildStatusIndex = $i + break + } + } - # Warning regex: similar pattern for warnings - $warningRegex = '^(?.*?)\((?\d+)[,)].*?warning (?(C|LNK)\d+): (?.*)$' + $linesToScan = $logContent + if ($buildFailed -and $buildStatusIndex -ne $null -and $buildStatusIndex + 1 -lt $logContent.Count) { + $linesToScan = $logContent[($buildStatusIndex + 1)..($logContent.Count - 1)] + } - foreach ($line in $logContent) { - # Check for errors - if ($line -match $errorRegex) { + # Broadened regex patterns to support linker/MSBuild messages without line numbers + $errorWithFileRegex = '^(?.+?)\((?\d+)[,)].*?\s(?fatal error|error)\s+(?(?:C|LNK|MSB)\d+)\s*:\s*(?.+)$' + $genericErrorRegex = '^(?:.*?:\s*)?(?fatal error|error)\s+(?(?:C|LNK|MSB)\d+)\s*:\s*(?.+)$' + $warningWithFileRegex = '^(?.+?)\((?\d+)[,)].*?\swarning\s+(?(?:C|LNK|MSB)\d+)\s*:\s*(?.+)$' + $genericWarningRegex = '^(?:.*?:\s*)?warning\s+(?(?:C|LNK|MSB)\d+)\s*:\s*(?.+)$' + + foreach ($line in $linesToScan) { + # Errors with file/line + if ($line -match $errorWithFileRegex) { $errors += [PSCustomObject]@{ File = $matches['file'] Line = [int]$matches['line'] @@ -168,9 +190,21 @@ try { Message = $matches['message'] RawLine = $line } + continue + } + # Generic errors (e.g., LINK/MSBuild without line numbers) + if ($line -match $genericErrorRegex) { + $errors += [PSCustomObject]@{ + File = '' + Line = $null + Code = $matches['code'] + Message = $matches['message'] + RawLine = $line + } + continue } - # Check for warnings - elseif ($line -match $warningRegex) { + # Warnings with file/line + if ($line -match $warningWithFileRegex) { $warnings += [PSCustomObject]@{ File = $matches['file'] Line = [int]$matches['line'] @@ -178,13 +212,29 @@ try { Message = $matches['message'] RawLine = $line } + continue + } + # Generic warnings + if ($line -match $genericWarningRegex) { + $warnings += [PSCustomObject]@{ + File = '' + Line = $null + Code = $matches['code'] + Message = $matches['message'] + RawLine = $line + } } } if ($errors.Count -gt 0) { Write-Host "`nFound $($errors.Count) error(s) in build log:" -ForegroundColor Red foreach ($e in $errors) { - Write-Host " $($e.File)($($e.Line)): error $($e.Code): $($e.Message)" -ForegroundColor Red + if ([string]::IsNullOrEmpty($e.File)) { + Write-Host " error $($e.Code): $($e.Message)" -ForegroundColor Red + } else { + $lineSuffix = if ($e.Line -ne $null) { "(" + $e.Line + ")" } else { "" } + Write-Host (" {0}{1}: error {2}: {3}" -f $e.File, $lineSuffix, $e.Code, $e.Message) -ForegroundColor Red + } } } else { Write-Host "`n✓ No errors found in build log" -ForegroundColor Green @@ -193,7 +243,12 @@ try { if ($warnings.Count -gt 0) { Write-Host "`nFound $($warnings.Count) warning(s) in build log:" -ForegroundColor Yellow foreach ($warning in $warnings | Select-Object -First 10) { - Write-Host " $($warning.File)($($warning.Line)): warning $($warning.Code): $($warning.Message)" -ForegroundColor Yellow + if ([string]::IsNullOrEmpty($warning.File)) { + Write-Host " warning $($warning.Code): $($warning.Message)" -ForegroundColor Yellow + } else { + $lineSuffix = if ($warning.Line -ne $null) { "(" + $warning.Line + ")" } else { "" } + Write-Host (" {0}{1}: warning {2}: {3}" -f $warning.File, $lineSuffix, $warning.Code, $warning.Message) -ForegroundColor Yellow + } } if ($warnings.Count -gt 10) { Write-Host " ... and $($warnings.Count - 10) more warnings" -ForegroundColor Gray @@ -202,6 +257,8 @@ try { } else { Write-Host "Log file is empty or could not be read" -ForegroundColor Yellow } + + # Timestamp freshness checks removed; rely on build log parsing instead } else { Write-Host "`nBuild log not found: $LogFile" -ForegroundColor Yellow } @@ -234,6 +291,7 @@ try { Errors = $errors Warnings = $warnings LogFile = $LogFile + # BuildLogTimestamp and stale artifact checks removed Message = $message } From e379310b0920b58b917f56337b07516529c47606 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Fri, 23 Jan 2026 12:47:40 -0500 Subject: [PATCH 48/68] add merge prompt file --- .github/prompt/merge.prompt.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/prompt/merge.prompt.md diff --git a/.github/prompt/merge.prompt.md b/.github/prompt/merge.prompt.md new file mode 100644 index 000000000000..d64421f7ef5c --- /dev/null +++ b/.github/prompt/merge.prompt.md @@ -0,0 +1,29 @@ +# Merge Upstream Prompt + +Assist with merging the commits from upstream into this branch starting from the provided GitHub tag or commit. + +Provide the following when you invoke this prompt: +- Start ref (tag or commit) — REQUIRED (e.g., `upstream/V_9_8_P1` or a commit SHA) +- Upstream remote — optional (default: `upstream`) +- Windows fork remote — optional (default: `upstream-pwsh`) +- Target branch — optional (default: current branch) + +Operating guidance: +- Use and follow merge-upstream.agent.md. Treat it as the primary operating guide. +- Rely on the provided for repository overview, setup, build, merge strategy, and testing. Do not re-fetch or re-search them; assume they are already attached in context. +- Perform chunked merges ending at commits with CI runs; build after each batch. Only parse build logs if the build fails. +- Build using the MCP tools: `mcp_openssh-server_Start_OpenSSHBuild` (Release/x64 by default). If the build fails, analyze with `mcp_openssh-server_Test_OpenSSHBuild`. +- Apply Windows compatibility strategies as documented (prefer win32compat layer; guard with `#ifdef WINDOWS` when necessary; update VS projects for build system changes). +- Summarize a plan, request approval between batches, and clearly list conflict resolutions and rationale. + +Expected outputs per batch: +- Planned commit range and rationale for the batch boundary +- Conflict resolutions (what, why, how), especially Windows-specific handling +- Build result summary and, on failure, parsed errors with applied fixes +- Next-step proposal and explicit ask to proceed + +Quick start examples: +- "Merge from tag `upstream/V_9_8_P1` into my current branch." +- "Merge starting at commit `3a1b2c3`, upstream remote `upstream`, target current branch." + +If the Start ref is not provided, ask for it before proceeding. From 5b55ec2ceb8519b4aba8eb0c21fce5fbb7f82bc4 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 23 Jan 2026 09:49:55 -0800 Subject: [PATCH 49/68] update tools --- .github/agents/merge-upstream.agent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index 36b8695b4809..e5b0e8f2b5dd 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -2,7 +2,7 @@ name: merge-upstream description: Assist with merging upstream OpenSSH commits into the PowerShell fork. tools: - ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'openssh-server/*', 'agent', 'todo'] + ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'openssh-server/*', 'todo'] --- # OpenSSH Upstream Merge Agent From 950bb1beae304ef8da15efa6bd8bb65de65967fe Mon Sep 17 00:00:00 2001 From: User Date: Fri, 23 Jan 2026 12:21:13 -0800 Subject: [PATCH 50/68] update instructions to build only after batch of commits with a CI run --- .../merge-process-overview.instructions.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 857ffcc7aa3f..f8b26c6847cc 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -112,14 +112,12 @@ The process consists of several interconnected phases: - Call tool with `StartCommit=`, `FirstChunkOnly=true`, `GroupByCIPresence=true` - Continue this process until the upstream's HEAD has been successfully merged -5. **Execute chunked merge (commit-by-commit within the chunk):** +5. **Execute chunked merge (batch cherry-pick all commits in chunk):** - **Important:** Even though commits are *grouped* into chunks for planning/CI boundaries, **cherry-pick each commit individually** within the chunk and **build after each commit**. This catches Windows-specific build breaks early and makes it clear which upstream commit introduced a regression. + **Important:** Cherry-pick **all commits in the batch** before building. This is more efficient than building after each individual commit. If the build fails, you can identify the problematic commit(s) through git bisect or by examining the build errors. ```pwsh - # For each chunk (start..end), enumerate and cherry-pick commits one at a time. - # Then build after EACH commit. - # + # For each chunk (start..end), cherry-pick all commits in the batch # Example for a single chunk: $chunkStart = $result.StartCommitFull $chunkEnd = $result.EndCommitFull @@ -128,11 +126,12 @@ The process consists of several interconnected phases: foreach ($commit in $commits) { Write-Host "Cherry-picking commit: $commit" git cherry-pick $commit - - # Build after every commit to attribute failures precisely. - # Use MCP Tool: mcp_openssh-server_Start_OpenSSHBuild - # Parameters: Configuration="Release", Architecture="x64" + + # If conflicts occur, resolve them before continuing to next commit + # (see step 7 below for conflict resolution) } + + # After all commits in batch are cherry-picked, proceed to build (step 9) ``` 7. **Resolve merge conflicts (per commit):** From ead71d7b3fbdcede7049a0e7a0e0c5c69e8fca37 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 23 Jan 2026 12:26:15 -0800 Subject: [PATCH 51/68] update prereq tool to use start-job with git cmd --- .github/tools/Test-MergePrerequisites.ps1 | 51 ++++++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/.github/tools/Test-MergePrerequisites.ps1 b/.github/tools/Test-MergePrerequisites.ps1 index bd807746fa40..dfd4c9c85972 100644 --- a/.github/tools/Test-MergePrerequisites.ps1 +++ b/.github/tools/Test-MergePrerequisites.ps1 @@ -82,7 +82,7 @@ try { Write-Host "" # Step 1: Verify Git - Write-Host "[1/5] Checking Git installation..." -ForegroundColor Cyan + Write-Host "[1/6] Checking Git installation..." -ForegroundColor Cyan try { $gitPath = Get-Command git -ErrorAction SilentlyContinue if ($gitPath) { @@ -98,7 +98,7 @@ try { } # Step 2: Verify PowerShell version - Write-Host "`n[2/5] Checking PowerShell version..." -ForegroundColor Cyan + Write-Host "`n[2/6] Checking PowerShell version..." -ForegroundColor Cyan $psVersion = $PSVersionTable.PSVersion if ($psVersion.Major -ge 5) { Write-Host " ✓ PowerShell $($psVersion.ToString()) (>= 5.0)" -ForegroundColor Green @@ -108,7 +108,7 @@ try { } # Step 3: Verify Visual Studio - Write-Host "`n[3/5] Checking Visual Studio installation..." -ForegroundColor Cyan + Write-Host "`n[3/6] Checking Visual Studio installation..." -ForegroundColor Cyan $msBuildPaths = @( "${env:ProgramFiles}\Microsoft Visual Studio\2022\*\MSBuild\Current\Bin\MSBuild.exe", "${env:ProgramFiles}\Microsoft Visual Studio\2019\*\MSBuild\Current\Bin\MSBuild.exe", @@ -133,7 +133,7 @@ try { } # Step 4: Verify repository remotes - Write-Host "`n[4/5] Checking repository remotes..." -ForegroundColor Cyan + Write-Host "`n[4/6] Checking repository remotes..." -ForegroundColor Cyan if ($result.GitInstalled) { try { $gitConfigPath = Join-Path $repoRoot ".git\config" @@ -169,7 +169,7 @@ try { } # Step 5: Verify target version exists - Write-Host "`n[5/5] Checking target version exists..." -ForegroundColor Cyan + Write-Host "`n[5/6] Checking target version exists..." -ForegroundColor Cyan if ($result.GitInstalled -and $result.RemotesConfigured) { try { # Check if tag/branch ref exists in .git directory @@ -195,6 +195,44 @@ try { Write-Host " ⊘ Skipped (prerequisites not met)" -ForegroundColor Yellow } + # Step 6: Verify working directory is clean + Write-Host "`n[6/6] Checking working directory status..." -ForegroundColor Cyan + if ($result.GitInstalled) { + try { + # Use Start-Job to run git status --porcelain + $job = Start-Job -ScriptBlock { + param($repoPath) + Set-Location $repoPath + git status --porcelain + } -ArgumentList $repoRoot + + $jobResult = Wait-Job $job -Timeout 10 + if ($jobResult) { + $statusOutput = Receive-Job $job + Remove-Job $job -Force + + if ([string]::IsNullOrWhiteSpace($statusOutput)) { + $result.WorkingDirClean = $true + Write-Host " ✓ Working directory is clean" -ForegroundColor Green + } else { + $result.Issues += "Working directory has uncommitted changes" + Write-Host " ✗ Working directory has uncommitted changes:" -ForegroundColor Red + $statusOutput | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow } + Write-Host " Hint: Commit or stash changes before starting merge" -ForegroundColor Yellow + } + } else { + Remove-Job $job -Force + $result.Issues += "Git status check timed out" + Write-Host " ✗ Git status check timed out" -ForegroundColor Red + } + } catch { + $result.Issues += "Error checking working directory: $($_.Exception.Message)" + Write-Host " ✗ Error checking working directory status" -ForegroundColor Red + } + } else { + Write-Host " ⊘ Skipped (Git not available)" -ForegroundColor Yellow + } + # Final evaluation Write-Host "`n========================================" -ForegroundColor Cyan Write-Host "PREREQUISITE CHECK SUMMARY" -ForegroundColor Cyan @@ -204,7 +242,8 @@ try { $result.GitInstalled, $result.VSInstalled, $result.RemotesConfigured, - $result.TargetExists + $result.TargetExists, + $result.WorkingDirClean ) $result.Success = ($criticalChecks | Where-Object { $_ -eq $false }).Count -eq 0 From 071bfba1e3fa2e5116e40770018ddb5fff92c87c Mon Sep 17 00:00:00 2001 From: User Date: Fri, 23 Jan 2026 12:32:01 -0800 Subject: [PATCH 52/68] update build instructions with paths.targets info --- .github/instructions/build.instructions.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md index 0dca61bd3fb9..435a3ede52e1 100644 --- a/.github/instructions/build.instructions.md +++ b/.github/instructions/build.instructions.md @@ -154,6 +154,28 @@ Only use this when running directly in a terminal (not via MCP): & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" .\contrib\win32\openssh\Win32-OpenSSH.sln /p:Configuration=Release /p:Platform=x64 /v:detailed ``` +### Important Note: paths.targets File Modification + +**The build process automatically modifies `.\contrib\win32\openssh\paths.targets`** to update SDK version paths based on the currently installed Windows SDK. This is normal and expected behavior. + +**Before committing any changes:** +```pwsh +# Check if paths.targets was modified by the build +git status .\contrib\win32\openssh\paths.targets + +# If it shows as modified, restore it to a clean state +git checkout .\contrib\win32\openssh\paths.targets +``` + +**Why this happens:** +- MSBuild automatically updates SDK paths to match your local environment +- These changes are environment-specific and should not be committed +- The file will be modified on every build + +**AI Agent Workflow:** +- After completing all build fixes and before final commit, restore paths.targets +- Only commit actual code changes, not build-generated path updates + ## Project File Management ### Understanding the Project Structure From 332176b17ec2a2e14819dc1de4adf90689d72f8b Mon Sep 17 00:00:00 2001 From: User Date: Fri, 23 Jan 2026 12:46:11 -0800 Subject: [PATCH 53/68] refine instructions --- .github/instructions/build.instructions.md | 17 +++++++++++++---- .../merge-process-overview.instructions.md | 16 ++++++++++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md index 435a3ede52e1..92c2ca11d842 100644 --- a/.github/instructions/build.instructions.md +++ b/.github/instructions/build.instructions.md @@ -126,7 +126,11 @@ fatal error C1083: Cannot open source file: 'newfile.c' 3. **If build failed**, **parse errors** using **Test-OpenSSHBuild** — `mcp_openssh-server_Test_OpenSSHBuild`. 4. **Categorize error types** (preprocessor, Windows compatibility, build system). 5. **Apply appropriate resolution strategy** (see error categories above) and **rebuild** with Start-OpenSSHBuild. -6. **Commit fixes with detailed message**. +6. **CRITICAL: Before committing, restore paths.targets**: + ```pwsh + git checkout .\contrib\win32\openssh\paths.targets + ``` +7. **Commit fixes with detailed message** (only actual code changes, not paths.targets). #### Reading Build Logs and Errors (on failure only) Use the **Test-OpenSSHBuild** MCP tool to read build logs and parse errors **only when the build fails**: @@ -172,9 +176,14 @@ git checkout .\contrib\win32\openssh\paths.targets - These changes are environment-specific and should not be committed - The file will be modified on every build -**AI Agent Workflow:** -- After completing all build fixes and before final commit, restore paths.targets -- Only commit actual code changes, not build-generated path updates +**AI Agent Workflow (CRITICAL):** +1. **ALWAYS restore paths.targets before committing**: + ```pwsh + # This MUST be done before every commit + git checkout .\contrib\win32\openssh\paths.targets + ``` +2. Only commit actual code changes, not build-generated path updates +3. Verify with `git status` that paths.targets is not staged before committing ## Project File Management diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index f8b26c6847cc..7b590a22a145 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -69,11 +69,19 @@ The process consists of several interconnected phases: git config core.editor true ``` -3. **Create merge branch from current branch:** +3. **Create merge branch (if not already created):** ```pwsh + # Check if branch already exists + git rev-parse --verify merge-v- 2>$null + + # If it doesn't exist, create it git checkout -b merge-v- # Example: merge-v9.8-20241010 + + # If it already exists, switch to it + git checkout merge-v- ``` + **Note:** The Test-MergePrerequisites tool will suggest the branch name. Create it once and reuse it throughout the merge process. ### Perform Merge with Grouped Commits 4. **Identify merge range and group commits:** @@ -164,7 +172,11 @@ The process consists of several interconnected phases: - **DO NOT** try to read log files directly with `Get-Content` or locate them manually - Fix issues based on parsed error output - Rebuild and verify - - Commit any build fixes separately with descriptive messages + - **CRITICAL: Before committing, restore paths.targets**: + ```pwsh + git checkout .\contrib\win32\openssh\paths.targets + ``` + - Commit any build fixes separately with descriptive messages (only actual code changes) 10. **Validate if batch ended with successful CI:** Check the end commit's CI status from Get-CommitGroups output. From 0bb51cd47730f5fdc0a7fbfb14000b88bf0471db Mon Sep 17 00:00:00 2001 From: User Date: Fri, 23 Jan 2026 12:49:00 -0800 Subject: [PATCH 54/68] tweak instructions --- .../merge/merge-process-overview.instructions.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 7b590a22a145..f50a9eb03ec1 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -69,20 +69,6 @@ The process consists of several interconnected phases: git config core.editor true ``` -3. **Create merge branch (if not already created):** - ```pwsh - # Check if branch already exists - git rev-parse --verify merge-v- 2>$null - - # If it doesn't exist, create it - git checkout -b merge-v- - # Example: merge-v9.8-20241010 - - # If it already exists, switch to it - git checkout merge-v- - ``` - **Note:** The Test-MergePrerequisites tool will suggest the branch name. Create it once and reuse it throughout the merge process. - ### Perform Merge with Grouped Commits 4. **Identify merge range and group commits:** Use the Get-CommitGroups MCP tool with `-FirstChunkOnly -GroupByCIPresence` From 29ad1162babdcd2aacbe3e7ef1f2fcec5f6eca72 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 23 Jan 2026 12:50:53 -0800 Subject: [PATCH 55/68] update tool --- .github/tools/Test-MergePrerequisites.ps1 | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/tools/Test-MergePrerequisites.ps1 b/.github/tools/Test-MergePrerequisites.ps1 index dfd4c9c85972..3cd75bd87657 100644 --- a/.github/tools/Test-MergePrerequisites.ps1 +++ b/.github/tools/Test-MergePrerequisites.ps1 @@ -251,11 +251,6 @@ try { if ($result.Success) { Write-Host "✓ ALL PREREQUISITES MET" -ForegroundColor Green Write-Host "`nYou are ready to begin the merge process:" -ForegroundColor White - Write-Host " 0. (Optional) Verify baseline build: mcp_openssh-server_Test_OpenSSHBuild" -ForegroundColor Gray - Write-Host " 1. Create merge branch: git checkout -b merge-$TargetVersion-$(Get-Date -Format 'yyyyMMdd')" -ForegroundColor Gray - Write-Host " 2. Use Get-CommitGroups tool to identify first batch of commits" -ForegroundColor Gray - Write-Host " 3. Begin cherry-picking commits from first batch" -ForegroundColor Gray - $result.Message = "All prerequisites met. Ready to start merge." } else { Write-Host "✗ PREREQUISITES NOT MET" -ForegroundColor Red From a1072a6099dad797252db312c3b1b8ad12bf8773 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 30 Jan 2026 10:30:24 -0800 Subject: [PATCH 56/68] fix bug in tool --- .github/tools/Get-CommitGroups.ps1 | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/tools/Get-CommitGroups.ps1 b/.github/tools/Get-CommitGroups.ps1 index 0c5411dcedb3..ef83730f31b2 100644 --- a/.github/tools/Get-CommitGroups.ps1 +++ b/.github/tools/Get-CommitGroups.ps1 @@ -254,21 +254,22 @@ try { $allCommits = @($comparison.commits) # If we hit the 250 commit limit, we need to get the actual first 250 commits - # by using the oldest commit from this batch as the end point - if ($comparison.total_commits -gt 250) { + # by iteratively narrowing the range until we get 250 or fewer commits + while ($comparison.total_commits -gt 250) { Write-Host "Warning: Total commits ($($comparison.total_commits)) exceeds API limit (250)." -ForegroundColor Yellow - Write-Host "Fetching first 250 commits by using oldest commit as endpoint..." -ForegroundColor Cyan + Write-Host "Fetching first 250 commits by narrowing the range..." -ForegroundColor Cyan - # Get the oldest commit SHA from the initial batch (first in chronological order) + # Get the oldest commit SHA from the current batch (first in chronological order) + # Since the batch is limited to 250, this is approximately the 250th commit from start $oldestCommitSha = $allCommits[0].sha - # Now compare from start to this oldest commit to get the actual first 250 + # Now compare from start to this oldest commit to narrow down the range $limitedCompareUrl = "$apiBase/compare/${startCommitSha}...${oldestCommitSha}" Write-Host "Comparing $startCommitSha...$oldestCommitSha" -ForegroundColor Gray - $limitedComparison = Invoke-RestMethod -Uri $limitedCompareUrl -Headers (Get-GitHubHeaders) + $comparison = Invoke-RestMethod -Uri $limitedCompareUrl -Headers (Get-GitHubHeaders) - $allCommits = @($limitedComparison.commits) - Write-Host "Fetched $($allCommits.Count) commits in the corrected range" -ForegroundColor Green + $allCommits = @($comparison.commits) + Write-Host "Narrowed to $($comparison.total_commits) total commits ($($allCommits.Count) returned)" -ForegroundColor Green } $startRef = if ($GitHubTag) { "tag $GitHubTag" } else { "commit $StartCommit" } From c19ad3f0af2d298bddf6c458d5cb6b480d698c47 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 30 Jan 2026 11:34:05 -0800 Subject: [PATCH 57/68] update instructions --- .github/instructions/build.instructions.md | 9 +++++++++ .../instructions/merge/merge-details.instructions.md | 11 ++++++++++- .../instructions/repository-overview.instructions.md | 7 ++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md index 92c2ca11d842..bcf6a3f8bc9f 100644 --- a/.github/instructions/build.instructions.md +++ b/.github/instructions/build.instructions.md @@ -110,6 +110,8 @@ fatal error C1083: Cannot open source file: 'newfile.c' ``` + + **Important:** Visual Studio project files (`.vcxproj`) use Windows-style line endings (`\r\n`). When programmatically adding lines to these files, ensure you use `\r\n` instead of just `\n` to maintain consistency with the existing file format. This prevents Git from showing the entire file as changed. 4. **Update solution if new binaries added:** ``` @@ -240,6 +242,13 @@ contrib\win32\openssh\ # Edit Win32-OpenSSH.sln to include new project ``` +**Important Note on Line Endings:** +Both `.vcxproj` and `.sln` files use Windows-style line endings (`\r\n`). When programmatically modifying these files: +- Use `\r\n` instead of `\n` when inserting new lines +- Maintain consistent line endings to avoid Git showing spurious changes +- Example in PowerShell: `$newLine = "`r`n"` +- Example in Python: `new_line = "\r\n"` + ## Validation and Testing ### Build Verification Using MCP Tools diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md index a2358a04cb2b..3f1df5e4e98b 100644 --- a/.github/instructions/merge/merge-details.instructions.md +++ b/.github/instructions/merge/merge-details.instructions.md @@ -199,6 +199,14 @@ FUNCTION sync_build_system(): FOR EACH source_file IN makefile_changes.new_source_files: target_project = determine_target_project(source_file) add_source_to_project(target_project, source_file) + +// CRITICAL: When modifying .vcxproj or .sln files programmatically, +// ALWAYS use Windows line endings (\r\n) instead of Unix (\n). +// This prevents Git from showing the entire file as modified. +FUNCTION add_source_to_project(project_file, source_file): + // Example: Use \r\n for line endings + new_line = f" \r\n" + insert_into_project_file(project_file, new_line) ``` ## Common Conflict Patterns @@ -209,9 +217,10 @@ FUNCTION sync_build_system(): - **File permissions** → Adapt to Windows ACL model ### Build System Changes -- **Makefile additions** → Update Visual Studio project files +- **Makefile additions** → Update Visual Studio project files (use `\r\n` line endings) - **New dependencies** → Check Windows compatibility - **Compiler flags** → Translate to MSVC equivalents +- **Project file edits** → Maintain Windows line endings (`\r\n`) to avoid Git diffs ### Configuration Changes - **New config options** → Add to `./contrib/win32/openssh/config.h.vs` diff --git a/.github/instructions/repository-overview.instructions.md b/.github/instructions/repository-overview.instructions.md index aa343214f394..3fe4c9d6c2b8 100644 --- a/.github/instructions/repository-overview.instructions.md +++ b/.github/instructions/repository-overview.instructions.md @@ -79,7 +79,12 @@ When merging changes to `ssh-agent.c` or related agent files from upstream: - Flag for Windows team review - May require significant redesign work -3. **Do NOT**: +3. **New files** (additional agent-related source files): + - **Not all new upstream files need to be ported** to the Windows ssh-agent + - Evaluate whether the functionality is relevant to the Windows implementation + - If not applicable, document the decision and skip porting + +4. **Do NOT**: - Directly apply upstream ssh-agent patches to Windows version - Assume one-to-one code correspondence - Merge without understanding Windows implementation differences From 2d2368cd04fdff490a4272bef306b38e52386606 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 20 Feb 2026 11:15:50 -0800 Subject: [PATCH 58/68] add instructions for communication expectations and examples --- .../agent-communication.instructions.md | 139 ++++++++ .../agent-communication-merge.instructions.md | 335 ++++++++++++++++++ 2 files changed, 474 insertions(+) create mode 100644 .github/instructions/agent-communication.instructions.md create mode 100644 .github/instructions/merge/agent-communication-merge.instructions.md diff --git a/.github/instructions/agent-communication.instructions.md b/.github/instructions/agent-communication.instructions.md new file mode 100644 index 000000000000..eb5242163023 --- /dev/null +++ b/.github/instructions/agent-communication.instructions.md @@ -0,0 +1,139 @@ +--- +applyTo: "**/*" +--- + +# AI Agent Communication Guidelines + +## Overview +This document establishes how AI agents should communicate with users throughout their interactions. These guidelines apply to **all agent operations** in this workspace. For merge-specific communication templates, see [agent-communication-merge.instructions.md](./merge/agent-communication-merge.instructions.md). + +## Core Principle: Tool Output vs Agent Communication + +### Distinction + +**1. MCP Tool Execution (Write-Host is acceptable):** +- When MCP tools execute PowerShell scripts, those scripts may use `Write-Host` for output +- This is normal and expected behavior for the tool itself +- The agent receives this output and can parse it +- Example: `Test-OpenSSHFunctionality.ps1` uses `Write-Host` to display test progress + +**2. Agent-to-User Communication (Use chat messages ONLY):** +- AI agents **MUST** communicate with users through chat messages, not `Write-Host` +- **DO NOT** invoke `Write-Host` or other console output commands from the agent +- Instead, present information conversationally in your responses +- Parse tool output and summarize it in chat messages + +## Communication Standards + +### ❌ INCORRECT: Agent Using Write-Host +```pwsh +Write-Host "Starting merge process..." +Write-Host "Build succeeded!" -ForegroundColor Green +Write-Host "Found 3 conflicts in auth.c" +``` + +### ✅ CORRECT: Agent Using Chat Messages +``` +Starting the merge process. I'll cherry-pick the commits in this batch and then build. + +The build succeeded! All 14 executables were created successfully. + +Found 3 conflicts in auth.c. Analyzing the conflict types to determine the resolution strategy. +``` + +## Pseudocode Implementation Guidance + +When you see pseudocode functions in instruction files like: +- `update_progress()` +- `generate_test_report()` +- `log()` +- `append_to_progress_log()` + +These represent **conceptual operations**. Implement them by: +1. Generating the information/report internally +2. Communicating the results to the user via chat messages +3. **NOT** by executing `Write-Host` or similar PowerShell output commands + +### Example: Pseudocode to Implementation + +**Pseudocode:** +```pseudocode +FUNCTION update_progress(phase, status, details): + log(f"Phase {phase}: {status}") + IF status == "FAILURE": + generate_failure_report(details) +``` + +**Correct Agent Implementation:** +``` +[Agent analyzes the phase and status] +[Agent sends chat message]: "Phase 1: Conflict Resolution - Completed successfully. Resolved 5 conflicts across 3 files." +``` + +**Incorrect Agent Implementation:** +```pwsh +Write-Host "Phase 1: Conflict Resolution - Completed successfully" +``` + +## Best Practices + +### 1. Be Conversational +- Write naturally, as if speaking to a colleague +- Avoid overly formal or robotic language +- Use active voice + +### 2. Provide Context +- Explain what you're doing and why +- Help users understand the current state +- Highlight important information + +### 3. Use Structured Formatting When Helpful +- Use markdown formatting for clarity +- Use bullet points for lists +- Use code blocks for commands or file paths +- Use file links when referencing specific files + +### 4. Progressive Disclosure +- Start with high-level summaries +- Provide details when relevant +- Don't overwhelm with unnecessary information + +### 5. Clear Status Indicators +Use clear language for status: +- ✅ "succeeded", "completed successfully", "passed" +- ❌ "failed", "encountered errors", "did not pass" +- ⚠️ "warning", "requires attention", "partial success" + +## Examples + +### Good: Progress Update +``` +Analyzing the upstream changes between the last merge commit and V_10_0_P2. Found 127 commits with 8 security fixes and 3 build system changes that will require Visual Studio project updates. +``` + +### Good: Error Report +``` +The build failed with 4 compilation errors: +- auth.c: Missing include for Windows compatibility header +- sshd.c: Undefined reference to fork() - needs Windows equivalent +- config.h.vs: Missing preprocessor definition for HAVE_SETRESUID + +I'll add the Windows compatibility fixes now. +``` + +### Good: Success Report +``` +Testing completed successfully! The SSH service installed, started, and accepted connections. The test command executed correctly with expected output. +``` + +## Merge-Specific Guidelines + +For detailed merge workflow communication templates, including exact formats for tool output summaries and commit batch summaries, see: +- [Merge-Specific Agent Communication Guidelines](./merge/agent-communication-merge.instructions.md) + +## Summary + +- **Tools can use Write-Host** - MCP tools and PowerShell scripts may output to console +- **Agents use chat only** - All agent communication must be via chat messages +- **Parse, don't echo** - Parse tool output and present summaries conversationally +- **Be clear and helpful** - Provide context, use formatting, and communicate status clearly diff --git a/.github/instructions/merge/agent-communication-merge.instructions.md b/.github/instructions/merge/agent-communication-merge.instructions.md new file mode 100644 index 000000000000..049cf62a09e0 --- /dev/null +++ b/.github/instructions/merge/agent-communication-merge.instructions.md @@ -0,0 +1,335 @@ +--- +applyTo: ".github/instructions/merge/**" +--- + +# Merge-Specific Agent Communication Guidelines + +## Overview +This document provides specific communication templates and guidelines for AI agents performing upstream merge operations. These guidelines **supplement** the high-level agent communication principles defined in [agent-communication.instructions.md](../agent-communication.instructions.md). + +**Key Points:** +- The high-level communication guidelines apply to ALL merge interactions +- This document adds merge-specific templates and formats +- Tool output templates are **MUST use** (exact format required) +- Batch summary templates are **SHOULD include** (flexible adaptation allowed) + +## MCP Tool Output Templates + +These templates define the **exact format** agents MUST use when communicating tool results to users. + +### 1. Start-OpenSSHBuild Tool Output + +**MUST use this format when reporting build results.** + +#### Success Scenario: +``` +Build completed successfully. All 14 executables were created: +- ssh.exe, sshd.exe, sshd-auth.exe, sshd-session.exe +- ssh-agent.exe, ssh-add.exe, ssh-keygen.exe, ssh-keyscan.exe +- scp.exe, sftp.exe, sftp-server.exe +- ssh-pkcs11-helper.exe, ssh-shellhost.exe, ssh-sk-helper.exe + +Configuration: Release | Architecture: x64 +``` + +#### Failure Scenario: +``` +Build failed with compilation errors. Analyzing the build log to identify issues. + +[After running Test-OpenSSHBuild] +Found X compilation errors: +- [file.c]: [brief error description] +- [file.c]: [brief error description] + +I'll resolve these errors now. +``` + +### 2. Test-OpenSSHBuild Tool Output + +**MUST use this format when reporting build error analysis.** + +#### When Build Failed: +``` +Build error analysis complete: + +Compilation Errors (X): +- [filename.c] (Line Y): [Error code] - [Brief description] +- [filename.c] (Line Y): [Error code] - [Brief description] + +Warnings (X): +- [filename.c] (Line Y): [Warning description] + +Error Categories: +- Missing preprocessor definitions: X +- Windows compatibility issues: X +- Build system inconsistencies: X +``` + +#### When Build Succeeded: +``` +No need to analyze build errors - the build completed successfully. +``` + +**Note:** Only invoke Test-OpenSSHBuild when Start-OpenSSHBuild reports failure. + +### 3. Test-OpenSSHFunctionality Tool Output + +**MUST use this format when reporting functionality test results.** + +#### Success Scenario: +``` +Functionality testing completed successfully! + +Test Results: +✅ Administrator privileges verified +✅ Test user created: [username] +✅ SSH service installed successfully +✅ SSH service started successfully +✅ Firewall rule configured +✅ SSH connection test passed +✅ Command execution verified: "echo hello world" returned expected output + +All test resources cleaned up. +``` + +#### Failure Scenario: +``` +Functionality testing failed. + +Test Progress: +✅ Administrator privileges verified +✅ Test user created: [username] +❌ SSH service installation failed + +Error: [Error message from tool] + +The test environment has been cleaned up. I'll investigate the service installation issue. +``` + +#### Partial Success Scenario: +``` +Functionality testing completed with warnings. + +Test Results: +✅ Administrator privileges verified +✅ Test user created: [username] +✅ SSH service installed successfully +✅ SSH service started successfully +⚠️ Firewall rule configuration skipped (SkipFirewall=true) +✅ SSH connection test passed +✅ Command execution verified + +Note: Firewall was not configured as requested. Ensure firewall rules exist if testing remote connections. +``` + +## Commit Batch Summary Template + +**SHOULD include these elements** when summarizing a commit batch. Adapt the format and detail level based on context. + +### Template Structure: + +``` +Processing batch [N] ([X] commits from [start_hash] to [end_hash]) + +Key Changes: +- [Category 1]: [Brief description] +- [Category 2]: [Brief description] +- [Category 3]: [Brief description] + +Files Modified: [X] files +Conflicts: [X] conflicts ([resolved/pending]) + +[Additional context if needed] +``` + +### Example - Simple Batch: +``` +Processing batch 1 (8 commits from a1b2c3d to e4f5a6b) + +Key Changes: +- Security fixes: Updated bounds checking in buffer handling +- Bug fixes: Corrected memory leak in session cleanup +- Documentation: Updated protocol specification comments + +Files Modified: 12 files +Conflicts: 2 conflicts (resolved) +``` + +### Example - Complex Batch: +``` +Processing batch 3 (15 commits from 7h8i9j0 to k1l2m3n) + +Key Changes: +- Build system: Added new source files for MLKEM support +- Authentication: Enhanced certificate validation logic +- Process management: Modified daemon initialization (requires Windows adaptation) +- Configuration: Added 3 new preprocessor definitions + +Files Modified: 28 files +Conflicts: 7 conflicts (5 resolved, 2 require manual review) + +Note: The daemon initialization changes use fork() which needs Windows CreateProcess() equivalent. +``` + +### Example - Minimal Batch: +``` +Processing batch 5 (3 commits from x7y8z9 to a0b1c2) + +Key Changes: +- Minor documentation updates +- Code formatting corrections + +Files Modified: 4 files +Conflicts: None +``` + +## Progress Update Templates + +**SHOULD include these elements** when communicating progress at different workflow phases. Adapt wording to context. + +### Pre-Merge Analysis Phase: +``` +Analyzing upstream changes from [last_merge_tag] to [target_version]... + +Found [X] commits to merge: +- [X] security fixes (HIGH PRIORITY) +- [X] bug fixes +- [X] feature additions +- [X] build system changes + +Grouping commits into batches for incremental merging. +``` + +### Conflict Resolution Phase: +``` +Resolving [X] conflicts in [filename]: + +- Conflict 1: [Brief description] → [Resolution strategy] +- Conflict 2: [Brief description] → [Resolution strategy] + +[X] conflicts resolved, [Y] remaining. +``` + +### Build Fixes Phase: +``` +Applying build fixes for iteration [N]: + +- Added Windows compatibility header to [file.c] +- Updated config.h.vs with [X] new definitions +- Added [source_file.c] to [project.vcxproj] + +Rebuilding... +``` + +### Testing Phase: +``` +Running functionality tests to validate the merge... + +[Use Test-OpenSSHFunctionality template from above] +``` + +### Cleanup Phase: +``` +Finalizing the merge: + +✅ Restored paths.targets to clean state +✅ Verified [X] commits applied successfully +✅ All builds passing +✅ Functionality tests passing + +Ready to commit and push. +``` + +## Special Scenarios + +### When Seeking User Approval: +``` +Batch [N] is ready for review: + +Summary: +- [X] commits merged successfully +- Build: ✅ Passed +- Tests: ✅ Passed +- Conflicts: [X] resolved + +Changes include: [brief list] + +May I proceed with the next batch? +``` + +### When Requesting Manual Intervention: +``` +I've encountered a complex conflict that requires manual review: + +File: [filename] +Issue: [Description of the conflict complexity] +Context: [Why automated resolution isn't appropriate] + +Recommendation: [Suggested approach] + +Please advise on how to proceed. +``` + +### When Reporting Critical Issues: +``` +⚠️ CRITICAL: [Issue type detected] + +Details: +- [Specific problem] +- [Impact assessment] +- [Affected components] + +This requires immediate attention before proceeding with the merge. +``` + +## Integration with Pseudocode + +When implementing pseudocode patterns from merge-details.instructions.md, use these communication approaches: + +### update_progress() → Chat Message +```pseudocode +# Pseudocode: +update_progress("Build Fixes", "IN_PROGRESS", "Applied 3 fixes") + +# Agent Implementation (via chat): +"Applying build fixes (iteration 2): Added 3 Windows compatibility changes. Rebuilding now..." +``` + +### generate_test_report() → Structured Chat Message +```pseudocode +# Pseudocode: +report = generate_test_report(test_results) + +# Agent Implementation (via chat): +[Use Test-OpenSSHFunctionality template from above] +``` + +### log() → Contextual Chat Message +```pseudocode +# Pseudocode: +log(f"Skipping {binary} - Unix only") + +# Agent Implementation (via chat): +"Skipping ssh-keysign.exe - Unix-only binary not applicable to Windows build." +``` + +## Best Practices for Merge Communication + +1. **Always include commit hashes** when referencing specific commits (first 7 characters) +2. **Use file links** when referencing specific files: [filename](path/to/filename) +3. **Categorize changes** by type (security, bug fix, feature, build system, etc.) +4. **Highlight Windows-specific considerations** that required special handling +5. **Provide commit counts** to show progress and scope +6. **Be specific about conflict types** and resolution strategies used +7. **Report before and after** major operations (build, test, etc.) +8. **Update after each batch** to maintain transparency +9. **Use consistent terminology** aligned with the instruction files +10. **Mark critical items** with appropriate indicators (⚠️, ✅, ❌) + +## Summary + +- **Tool templates are REQUIRED** - Use exact formats for MCP tool output summaries +- **Batch templates are RECOMMENDED** - Include suggested elements but adapt to context +- **Supplement high-level guidelines** - All general communication principles still apply +- **Maintain transparency** - Keep users informed throughout the merge process +- **Be consistent** - Use standard terminology and formatting patterns From 179d41ef8bfac06cbc56df428e67e8d76846cfca Mon Sep 17 00:00:00 2001 From: User Date: Fri, 20 Feb 2026 11:26:19 -0800 Subject: [PATCH 59/68] update instructions and tools to clarify end commit expectations --- .github/agents/merge-upstream.agent.md | 15 +++++++------ .../merge-process-overview.instructions.md | 8 ++++--- .github/prompt/merge.prompt.md | 3 +++ .github/tools/Get-CommitGroups.ps1 | 21 ++++++++++++++++--- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index e5b0e8f2b5dd..3909a0b7138e 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -29,6 +29,7 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - **Parameters**: - `GitHubTag` (string, optional): GitHub tag to start from (e.g., "V_10_0_P2") - `StartCommit` (string, optional): Commit SHA to start from + - `EndCommit` (string, optional): Commit SHA to end at (default: HEAD - most recent upstream commit) - `FirstChunkOnly` (boolean, optional): Stop after finding first chunk - `GroupByCIPresence` (boolean, optional): Group by CI presence instead of CI success - **Recommended Usage**: Always use `-FirstChunkOnly -GroupByCIPresence` for incremental merging @@ -92,7 +93,8 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for 1. **Run prerequisite verification via MCP tool:** - **MCP Tool Name**: `mcp_openssh-server_Test_MergePrerequisites` - **Parameters**: - - `TargetVersion` (string, required): Upstream version/tag to merge (e.g., "V_10_0_P2") + - `TargetVersion` (string, required): Upstream version/tag to start from (e.g., "V_10_0_P2") + - `EndCommit` (string, optional): Commit SHA to end at (default: HEAD - most recent upstream commit) - `SkipBaselineBuild` (boolean, optional): Skip baseline build check (default: false) This single tool verifies: @@ -126,8 +128,9 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for 1. **Get first commit batch** using Get-CommitGroups MCP tool: - **MCP Tool Name**: `mcp_openssh-server_Get_CommitGroups` - **Parameters**: - - For first batch: `GitHubTag="V_10_0_P2"`, `FirstChunkOnly=true`, `GroupByCIPresence=true` - - For subsequent batches: `StartCommit=""`, `FirstChunkOnly=true`, `GroupByCIPresence=true` + - For first batch: `GitHubTag="V_10_0_P2"`, `EndCommit=""`, `FirstChunkOnly=true`, `GroupByCIPresence=true` + - For subsequent batches: `StartCommit=""`, `EndCommit=""`, `FirstChunkOnly=true`, `GroupByCIPresence=true` + - **Note**: If `EndCommit` is not specified, the tool will merge up to the most recent upstream commit (HEAD) **The tool returns structured data:** ```json @@ -257,14 +260,14 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for **Objective:** Process remaining commit groups until merge is complete **Steps:** -1. **Return to Phase 2** with `-StartCommit` set to previous batch's end commit +1. **Return to Phase 2** with `-StartCommit` set to previous batch's end commit and `-EndCommit` set to target end commit 2. **Repeat Phases 2-4** for each batch: - - Get next batch with Get-CommitGroups + - Get next batch with Get-CommitGroups (passing both StartCommit and EndCommit) - Cherry-pick commits - Build (mandatory) - Validate (if batch ends with successful CI) - Summarize and get approval -3. **Continue** until all target commits are merged or HEAD is reached +3. **Continue** until the target end commit is reached (or HEAD if no end commit was specified) 4. **Perform final comprehensive validation:** - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - **Parameters**: (use defaults for Release/x64) diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index f50a9eb03ec1..87d273eafea2 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -78,12 +78,14 @@ The process consists of several interconnected phases: **Parameters**: - `GitHubTag` (string, optional): Start from last merged tag (e.g., "V_10_0_P2") - `StartCommit` (string, optional): Start from specific commit SHA + - `EndCommit` (string, optional): End at specific commit SHA (default: HEAD - most recent upstream commit) - `FirstChunkOnly` (boolean): Set to `true` - `GroupByCIPresence` (boolean): Set to `true` **Example for first batch**: - Find the last upstream tag in the fork - - Call tool with `GitHubTag=`, `FirstChunkOnly=true`, `GroupByCIPresence=true` + - Call tool with `GitHubTag=`, `EndCommit=`, `FirstChunkOnly=true`, `GroupByCIPresence=true` + - Omit `EndCommit` to merge all commits up to HEAD (most recent upstream commit) - This gets commits ending with any commit that has CI runs (not just successful CI) **Example output:** @@ -103,8 +105,8 @@ The process consists of several interconnected phases: Display batch details for verification, then proceed with cherry-picking. **After completing steps below, get next batch**: - - Call tool with `StartCommit=`, `FirstChunkOnly=true`, `GroupByCIPresence=true` - - Continue this process until the upstream's HEAD has been successfully merged + - Call tool with `StartCommit=`, `EndCommit=`, `FirstChunkOnly=true`, `GroupByCIPresence=true` + - Continue this process until the target end commit is reached (or HEAD if no end commit specified) 5. **Execute chunked merge (batch cherry-pick all commits in chunk):** diff --git a/.github/prompt/merge.prompt.md b/.github/prompt/merge.prompt.md index d64421f7ef5c..a75572557e79 100644 --- a/.github/prompt/merge.prompt.md +++ b/.github/prompt/merge.prompt.md @@ -4,6 +4,7 @@ Assist with merging the commits from upstream into this branch starting from the Provide the following when you invoke this prompt: - Start ref (tag or commit) — REQUIRED (e.g., `upstream/V_9_8_P1` or a commit SHA) +- End ref (commit) — OPTIONAL (default: HEAD - most recent upstream commit) - Upstream remote — optional (default: `upstream`) - Windows fork remote — optional (default: `upstream-pwsh`) - Target branch — optional (default: current branch) @@ -24,6 +25,8 @@ Expected outputs per batch: Quick start examples: - "Merge from tag `upstream/V_9_8_P1` into my current branch." +- "Merge from tag `upstream/V_9_8_P1` to commit `a1b2c3d` into my current branch." - "Merge starting at commit `3a1b2c3`, upstream remote `upstream`, target current branch." If the Start ref is not provided, ask for it before proceeding. +If the End ref is not provided, merging will continue to HEAD (most recent upstream commit). diff --git a/.github/tools/Get-CommitGroups.ps1 b/.github/tools/Get-CommitGroups.ps1 index ef83730f31b2..c2e2b18049d8 100644 --- a/.github/tools/Get-CommitGroups.ps1 +++ b/.github/tools/Get-CommitGroups.ps1 @@ -21,9 +21,14 @@ .PARAMETER StartCommit The commit SHA to start from (e.g., "6fb728df50c1afd338cb0223a84ce24579577eff"). - Cannot be used with -GitHubTag. The script will find commits after this commit up to HEAD. + Cannot be used with -GitHubTag. The script will find commits after this commit up to EndCommit (or HEAD if not specified). This is typically used when continuing from a previously merged commit. +.PARAMETER EndCommit + The commit SHA to end at (e.g., "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"). + If not specified, defaults to HEAD (most recent upstream commit). + This allows merging up to a specific commit rather than always merging to HEAD. + .PARAMETER FirstChunkOnly When specified, the script stops after finding the first chunk with a successful CI commit (or first chunk with any CI when using -GroupByCIPresence). @@ -66,6 +71,12 @@ Finds the first batch of commits after the V_10_0_P2 tag that ends with any commit that has CI runs (regardless of success or failure). +.EXAMPLE + .\Get-CommitGroups.ps1 -StartCommit "6fb728df50c1afd338cb0223a84ce24579577eff" -EndCommit "a1b2c3d4e5f6" -FirstChunkOnly + + Finds the first batch of commits between the specified start and end commits. + Useful for merging a specific range rather than all commits up to HEAD. + .NOTES - Requires internet access to query GitHub API - Set GITHUB_TOKEN environment variable for authenticated API access (5,000 requests/hour) @@ -86,6 +97,9 @@ param( [Parameter(Mandatory=$false)] [string]$StartCommit, + [Parameter(Mandatory=$false)] + [string]$EndCommit, + [Parameter(Mandatory=$false)] [switch]$FirstChunkOnly, @@ -242,11 +256,12 @@ try { $page = 1 $perPage = 250 # Compare API returns max 250 commits per page - Write-Host "Fetching commits from $startCommitSha...HEAD" -ForegroundColor Gray + $endRef = if ($EndCommit) { $EndCommit } else { "HEAD" } + Write-Host "Fetching commits from $startCommitSha...$endRef" -ForegroundColor Gray # The Compare API doesn't support pagination, so we need to use commits API instead # to get commits in the correct range with proper pagination - $compareUrl = "$apiBase/compare/${startCommitSha}...HEAD" + $compareUrl = "$apiBase/compare/${startCommitSha}...$endRef" $comparison = Invoke-RestMethod -Uri $compareUrl -Headers (Get-GitHubHeaders) # The Compare API returns commits - need to verify order From 7e1ea03f1344edfa48b2964fcdf36bb2056caeb5 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 20 Feb 2026 11:33:49 -0800 Subject: [PATCH 60/68] add instructions around addressing compiler warnings --- .github/agents/merge-upstream.agent.md | 10 ++- .github/instructions/build.instructions.md | 57 +++++++++++++++- .../agent-communication-merge.instructions.md | 67 ++++++++++++++++++- .../merge/merge-details.instructions.md | 21 ++++-- .../merge-process-overview.instructions.md | 26 +++++-- 5 files changed, 164 insertions(+), 17 deletions(-) diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index 3909a0b7138e..bb67a39cefbb 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -110,7 +110,14 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - Tool must display "ALL PREREQUISITES MET" - If tool fails, fix reported issues before continuing -3. **Create merge branch:** +3. **Establish baseline warning count:** + - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHBuild` + - **Parameters**: `Configuration="Release"`, `Architecture="x64"` + - Parse and document the current warning count and categories + - This baseline will be used to detect new warnings introduced during merge + - Store baseline for comparison after each build + +4. **Create merge branch:** ```pwsh git checkout -b merge-v- # Example: git checkout -b merge-v10.0P2-20260109 @@ -118,6 +125,7 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for **Success Criteria:** - Prerequisite tool reports all checks passed +- Baseline warning count established and documented - Merge branch created - Ready to begin Phase 2 (cherry-picking first batch) diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md index bcf6a3f8bc9f..23724ae0d8f8 100644 --- a/.github/instructions/build.instructions.md +++ b/.github/instructions/build.instructions.md @@ -47,6 +47,55 @@ Use the Test-OpenSSHBuild MCP tool when a build fails: - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` +## Compiler Warning Policy + +### Overview +All new compiler warnings introduced during the merge process must be reported to users and require approval before proceeding. This ensures code quality is maintained and potential issues are not overlooked. + +### Baseline Establishment +1. **Before starting the merge**, establish a baseline warning count: + - Run Test-OpenSSHBuild on the current branch (before any merge commits) + - Document the total warning count and warning categories + - Store this as the baseline for comparison + +2. **After each build during merge**, compare warnings against baseline: + - Run Test-OpenSSHBuild after every successful build (not just failures) + - Compare current warning count to baseline + - Identify any new warnings introduced + +### Warning Categorization +Attempt to categorize warnings to help users make informed decisions: + +**Common Warning Categories:** +- **Deprecated APIs**: Use of functions/APIs marked as deprecated +- **Type Conversions**: Implicit type conversions that may lose data +- **Unused Variables/Functions**: Declared but unused code elements +- **Potential Bugs**: Logic issues that may cause runtime problems +- **Security-Related**: Potential security vulnerabilities (buffer overruns, etc.) +- **Platform-Specific**: Windows-specific compatibility warnings + +**Note:** Categorization helps users make decisions, but **all new warnings require user approval regardless of predicted severity**. + +### User Approval Requirement +**Critical Rule**: No threshold - every new warning requires user input. + +1. **When new warnings are detected:** + - Report warning count delta (baseline vs current) + - List each new warning with: + - File and line number + - Warning code and message + - Attempted category classification + - Request user decision: fix warnings or proceed as-is + +2. **User must explicitly approve:** + - Fixing warnings before continuing + - Proceeding with warnings (acknowledging they will remain) + - Do NOT automatically proceed if new warnings appear + +3. **Update baseline if user approves proceeding:** + - If user approves proceeding with new warnings, update baseline + - This prevents re-reporting the same warnings in subsequent batches + ## Compilation Error Resolution ### Common Error Categories @@ -151,8 +200,12 @@ Use the **Test-OpenSSHBuild** MCP tool to read build logs and parse errors **onl ### Build Tools Invocation Policy - Use `Start-OpenSSHBuild.ps1` to run the build for each chunk/batch. -- Only if `Start-OpenSSHBuild.ps1` reports the build failed, invoke `Test-OpenSSHBuild.ps1` to parse errors and warnings from the build log. -- Skip `Test-OpenSSHBuild.ps1` when the build succeeded to avoid unnecessary log parsing. +- **ALWAYS invoke `Test-OpenSSHBuild.ps1` after every build** (success or failure): + - On build failure: Parse errors and warnings to fix issues + - On build success: Parse warnings to compare against baseline +- Compare warning count against established baseline +- If new warnings detected, report to user and request approval before proceeding +- Do NOT skip `Test-OpenSSHBuild.ps1` even when build succeeds - warning checks are mandatory. #### Alternative: Direct MSBuild (Terminal Only) Only use this when running directly in a terminal (not via MCP): diff --git a/.github/instructions/merge/agent-communication-merge.instructions.md b/.github/instructions/merge/agent-communication-merge.instructions.md index 049cf62a09e0..27c49018c943 100644 --- a/.github/instructions/merge/agent-communication-merge.instructions.md +++ b/.github/instructions/merge/agent-communication-merge.instructions.md @@ -44,6 +44,35 @@ Found X compilation errors: I'll resolve these errors now. ``` +#### Success with New Warnings Scenario: +**MUST use this format when build succeeds but new warnings are detected.** + +``` +Build completed successfully. All 14 executables were created. + +However, new compiler warnings have been introduced: + +Baseline: X warnings → Current: Y warnings (ΔZ new warnings) + +New Warnings Detected: + +**Deprecated APIs (2):** +- [auth.c] (Line 145): C4996 - 'strcpy': This function may be unsafe. Consider using strcpy_s instead. +- [sshd.c] (Line 302): C4996 - 'GetVersion': was declared deprecated + +**Type Conversions (1):** +- [channels.c] (Line 89): C4244 - Conversion from 'size_t' to 'int', possible loss of data + +**Analysis:** +These warnings appear to be introduced by upstream changes. The deprecated API warnings may require Windows-specific alternatives, and the type conversion should be reviewed for potential data loss. + +How would you like to proceed? +1. Fix these warnings before continuing +2. Proceed with warnings (will update baseline) + +Please advise. +``` + ### 2. Test-OpenSSHBuild Tool Output **MUST use this format when reporting build error analysis.** @@ -65,12 +94,44 @@ Error Categories: - Build system inconsistencies: X ``` -#### When Build Succeeded: +#### When Build Succeeded with No New Warnings: ``` -No need to analyze build errors - the build completed successfully. +Build analysis complete: + +Compilation Errors: 0 +Warnings: X (no change from baseline) + +No new warnings introduced. Build is clean. +``` + +#### When Build Succeeded with New Warnings: +``` +Build analysis complete: + +Compilation Errors: 0 + +Warnings Comparison: +- Baseline: X warnings +- Current: Y warnings +- New warnings: Z + +New Warnings by Category: + +**Deprecated APIs (2):** +- [auth.c] (Line 145): C4996 - 'strcpy': This function may be unsafe +- [sshd.c] (Line 302): C4996 - 'GetVersion': was declared deprecated + +**Type Conversions (1):** +- [channels.c] (Line 89): C4244 - Conversion from 'size_t' to 'int' + +**Potential Bugs (0)** +**Security-Related (0)** +**Platform-Specific (0)** + +These new warnings require user approval before proceeding. ``` -**Note:** Only invoke Test-OpenSSHBuild when Start-OpenSSHBuild reports failure. +**Note:** ALWAYS invoke Test-OpenSSHBuild after every build to check for warnings, not just on failure. ### 3. Test-OpenSSHFunctionality Tool Output diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md index 3f1df5e4e98b..d69e2cf67b09 100644 --- a/.github/instructions/merge/merge-details.instructions.md +++ b/.github/instructions/merge/merge-details.instructions.md @@ -265,11 +265,20 @@ FUNCTION automated_build_fix(): // Always start with Start-OpenSSHBuild.ps1 build_result = start_openssh_build(Configuration="Release", Architecture="x64") + // ALWAYS invoke Test-OpenSSHBuild.ps1 to check warnings (success or failure) + test_result = test_openssh_build(Configuration="Release", Architecture="x64", LogFile=build_result.log) + IF build_result.success: + // Check for new warnings against baseline + new_warnings = compare_warnings_to_baseline(test_result.warnings, baseline_warnings) + IF new_warnings.count > 0: + categorized_warnings = categorize_warnings(new_warnings) + request_user_approval(categorized_warnings) + // Wait for user decision: fix warnings or proceed RETURN SUCCESS - // Only invoke Test-OpenSSHBuild.ps1 when build failed - errors = test_openssh_build(Configuration="Release", Architecture="x64", LogFile=build_result.log) + // Build failed - parse errors + errors = test_result.errors fixes_applied = [] FOR EACH error IN errors: @@ -303,8 +312,12 @@ FUNCTION determine_fix_strategy(error): ### Build Tools Invocation Policy - Use `Start-OpenSSHBuild.ps1` to run the build for each chunk/batch. -- Only if `Start-OpenSSHBuild.ps1` reports the build failed, invoke `Test-OpenSSHBuild.ps1` to parse errors and warnings from the build log. -- Skip `Test-OpenSSHBuild.ps1` when the build succeeded to avoid unnecessary log parsing. +- **ALWAYS invoke `Test-OpenSSHBuild.ps1` after every build** (success or failure): + - On build failure: Parse errors and warnings to fix issues + - On build success: Parse warnings to compare against baseline +- Compare warning count against established baseline +- If new warnings detected, report to user with categorization and request approval before proceeding +- Do NOT skip `Test-OpenSSHBuild.ps1` even when build succeeds - warning checks are mandatory. ## Testing Automation Framework diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 87d273eafea2..a50405864a80 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -153,18 +153,30 @@ The process consists of several interconnected phases: - **MCP Tool Name**: `mcp_openssh-server_Start_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` - If build fails: - - **Use Test-OpenSSHBuild MCP tool to read the build log and parse errors**: + **ALWAYS check warnings after build (success or failure):** + - **Use Test-OpenSSHBuild MCP tool to parse errors and warnings**: - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` - **DO NOT** try to read log files directly with `Get-Content` or locate them manually + + If build failed: - Fix issues based on parsed error output - Rebuild and verify - - **CRITICAL: Before committing, restore paths.targets**: - ```pwsh - git checkout .\contrib\win32\openssh\paths.targets - ``` - - Commit any build fixes separately with descriptive messages (only actual code changes) + + If build succeeded: + - Compare warnings against baseline established in Phase 1 + - If new warnings detected: + - Categorize warnings (deprecated APIs, type conversions, potential bugs, etc.) + - Report to user with warning details and categories + - Request user decision: fix warnings or proceed + - Wait for user approval before continuing + - If user approves proceeding, update baseline to include new warnings + + **CRITICAL: Before committing, restore paths.targets**: + ```pwsh + git checkout .\contrib\win32\openssh\paths.targets + ``` + Commit any build fixes separately with descriptive messages (only actual code changes) 10. **Validate if batch ended with successful CI:** Check the end commit's CI status from Get-CommitGroups output. From c19afda80e1f663baae9a378cfe4af7d8dec47d3 Mon Sep 17 00:00:00 2001 From: User Date: Fri, 6 Mar 2026 09:38:13 -0800 Subject: [PATCH 61/68] add tool to wrap GH commands to check exit code --- .github/instructions/build.instructions.md | 26 +- .../merge/merge-details.instructions.md | 17 +- .../merge-process-overview.instructions.md | 62 ++- .../merge/research.instructions.md | 17 +- .github/instructions/setup.instructions.md | 8 +- .github/tools/Invoke-Git.ps1 | 406 ++++++++++++++++++ .github/tools/Test-OpenSSHFunctionality.ps1 | 14 +- 7 files changed, 495 insertions(+), 55 deletions(-) create mode 100644 .github/tools/Invoke-Git.ps1 diff --git a/.github/instructions/build.instructions.md b/.github/instructions/build.instructions.md index 23724ae0d8f8..fd2a73856920 100644 --- a/.github/instructions/build.instructions.md +++ b/.github/instructions/build.instructions.md @@ -143,8 +143,9 @@ fatal error C1083: Cannot open source file: 'newfile.c' **AI Agent Resolution Process:** 1. **Check Makefile changes:** - ```bash - git diff upstream-pwsh/latestw_all upstream/ -- Makefile.in + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Diff", Range="upstream-pwsh/latestw_all..upstream/", Path="Makefile.in" ``` 2. **Identify new/removed source files:** @@ -179,7 +180,8 @@ fatal error C1083: Cannot open source file: 'newfile.c' 5. **Apply appropriate resolution strategy** (see error categories above) and **rebuild** with Start-OpenSSHBuild. 6. **CRITICAL: Before committing, restore paths.targets**: ```pwsh - git checkout .\contrib\win32\openssh\paths.targets + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Checkout", Target=".\contrib\win32\openssh\paths.targets" ``` 7. **Commit fixes with detailed message** (only actual code changes, not paths.targets). @@ -219,11 +221,14 @@ Only use this when running directly in a terminal (not via MCP): **Before committing any changes:** ```pwsh -# Check if paths.targets was modified by the build -git status .\contrib\win32\openssh\paths.targets - -# If it shows as modified, restore it to a clean state -git checkout .\contrib\win32\openssh\paths.targets +# Check if paths.targets was modified by the build: +# MCP Tool: mcp_openssh-server_Invoke_Git +# Operation="Status" +# Check if paths.targets appears in result.ModifiedFiles + +# If it shows as modified, restore it to a clean state: +# MCP Tool: mcp_openssh-server_Invoke_Git +# Operation="Checkout", Target=".\contrib\win32\openssh\paths.targets" ``` **Why this happens:** @@ -235,10 +240,11 @@ git checkout .\contrib\win32\openssh\paths.targets 1. **ALWAYS restore paths.targets before committing**: ```pwsh # This MUST be done before every commit - git checkout .\contrib\win32\openssh\paths.targets + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Checkout", Target=".\contrib\win32\openssh\paths.targets" ``` 2. Only commit actual code changes, not build-generated path updates -3. Verify with `git status` that paths.targets is not staged before committing +3. Verify with Invoke-Git `Operation="Status"` that paths.targets is not in `ModifiedFiles` before committing ## Project File Management diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md index d69e2cf67b09..875171e54b27 100644 --- a/.github/instructions/merge/merge-details.instructions.md +++ b/.github/instructions/merge/merge-details.instructions.md @@ -84,16 +84,18 @@ FUNCTION resolve_conflict(file_path, conflict_content): ### Primary References - [Upstream release notes](https://www.openssh.com/releasenotes.html) - Pay special attention when merging new versions - [Previous merge PRs](./research.instructions.md) - Review conflict resolution patterns -- Commit history and messages - Use `git log --oneline upstream/` to understand changes +- Commit history and messages - Use Invoke-Git `Operation="Log"`, `Range="..upstream/"` to understand changes - Local repository file comparison - Use 3-way diff tools ### Analysis Commands ```pwsh -# View commit details for understanding changes -git show +# View commit details for understanding changes: +# MCP Tool: mcp_openssh-server_Invoke_Git +# Operation="Show", CommitHash="" -# Compare files between branches -git diff HEAD upstream/ -- +# Compare files between branches: +# MCP Tool: mcp_openssh-server_Invoke_Git +# Operation="Diff", Range="HEAD..upstream/", Path="" ``` ## Conflict Resolution Strategies @@ -231,8 +233,9 @@ FUNCTION add_source_to_project(project_file, source_file): ### For Each Conflict: 1. **Analyze the change** - ```bash - git show upstream/ -- + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Show", CommitHash="upstream/", Path="" ``` 2. **Check previous resolutions** diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index a50405864a80..4f64bf99b092 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -66,7 +66,8 @@ The process consists of several interconnected phases: 2. **Configure git:** ```pwsh - git config core.editor true + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Config", Key="core.editor", Value="true" ``` ### Perform Merge with Grouped Commits @@ -117,11 +118,16 @@ The process consists of several interconnected phases: # Example for a single chunk: $chunkStart = $result.StartCommitFull $chunkEnd = $result.EndCommitFull - $commits = git rev-list --reverse "$chunkStart^..$chunkEnd" - foreach ($commit in $commits) { - Write-Host "Cherry-picking commit: $commit" - git cherry-pick $commit + # Get commits in oldest-first order using Invoke-Git MCP tool: + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Log", Range="$chunkStart^..$chunkEnd", ShasOnly=true + # result.Commits contains [{Hash, Message}] in oldest-first order + + foreach ($commit in $result.Commits) { + # Cherry-pick each commit using Invoke-Git MCP tool: + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CherryPick", CommitHash=$commit.Hash # If conflicts occur, resolve them before continuing to next commit # (see step 7 below for conflict resolution) @@ -141,8 +147,14 @@ The process consists of several interconnected phases: 8. **Continue cherry-picking after resolution:** ```pwsh # After resolving conflicts for current commit - git add . - git cherry-pick --continue + + # Stage all resolved files using Invoke-Git MCP tool: + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Add", Path="." + + # Continue cherry-pick using Invoke-Git MCP tool: + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CherryPickContinue" # Then continue with remaining commits in batch # Repeat process for next batch if needed @@ -174,7 +186,8 @@ The process consists of several interconnected phases: **CRITICAL: Before committing, restore paths.targets**: ```pwsh - git checkout .\contrib\win32\openssh\paths.targets + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Checkout", Target=".\contrib\win32\openssh\paths.targets" ``` Commit any build fixes separately with descriptive messages (only actual code changes) @@ -197,9 +210,9 @@ The process consists of several interconnected phases: **📖 Detailed Instructions:** [Build Instructions](../build.instructions.md) 7. **Initial build attempt:** - ```pwsh - Start-OpenSSHBuild -Configuration Release -NativeHostArch x64 - ``` + Use the Start-OpenSSHBuild MCP tool: + - **MCP Tool Name**: `mcp_openssh-server_Start_OpenSSHBuild` + - **Parameters**: `Configuration="Release"`, `Architecture="x64"` 8. **Resolve compilation errors (iterative process):** @@ -217,12 +230,14 @@ The process consists of several interconnected phases: 10. **Commit build fixes:** ```pwsh - git commit -m "Fix compilation errors for - - Changes: - - Updated config.h.vs with - - Added Windows equivalent for - - Updated project files for " + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Commit" + # Message="Fix compilation errors for + # + # Changes: + # - Updated config.h.vs with + # - Added Windows equivalent for + # - Updated project files for " ``` --- @@ -245,10 +260,12 @@ The process consists of several interconnected phases: 13. **Commit any test fixes:** ```pwsh - git commit -m "Fix runtime issues for - - Issues resolved: - - " + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Commit" + # Message="Fix runtime issues for + # + # Issues resolved: + # - " ``` --- @@ -258,7 +275,8 @@ The process consists of several interconnected phases: ### Creating the Pull Request 14. **Push to fork:** ```pwsh - git push origin merge-v- + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Push", Remote="origin", Branch="merge-v-" ``` 15. **Create Pull Request:** diff --git a/.github/instructions/merge/research.instructions.md b/.github/instructions/merge/research.instructions.md index 49b6b56a4226..3259ac577037 100644 --- a/.github/instructions/merge/research.instructions.md +++ b/.github/instructions/merge/research.instructions.md @@ -54,16 +54,19 @@ the AI should flag this as requiring Windows event mechanism adaptation. ## Upstream Repository Analysis -**Commands for AI Agent:** +**Commands for AI Agent (use Invoke-Git MCP tool):** ```pwsh -# Get commit history for target version -git log --oneline upstream/ --since="" +# Get commit history for target version: +# MCP Tool: mcp_openssh-server_Invoke_Git +# Operation="Log", Range="..upstream/" -# Analyze specific commits -git show +# Analyze specific commits: +# MCP Tool: mcp_openssh-server_Invoke_Git +# Operation="Show", CommitHash="" -# Compare branches -git diff HEAD upstream/ +# Compare branches: +# MCP Tool: mcp_openssh-server_Invoke_Git +# Operation="Diff", Range="HEAD..upstream/" ``` ## Windows-Specific Knowledge Base diff --git a/.github/instructions/setup.instructions.md b/.github/instructions/setup.instructions.md index 5ec3962af00f..00bb5b88746f 100644 --- a/.github/instructions/setup.instructions.md +++ b/.github/instructions/setup.instructions.md @@ -35,9 +35,9 @@ git remote -v ``` ### Step 4: Initial Fetch -```pwsh -git fetch --all -``` +Use the Invoke-Git MCP tool to fetch from all remotes: +- **MCP Tool**: `mcp_openssh-server_Invoke_Git` +- **Operation**: `Fetch`, **Remote**: `all` ## Branch Strategy @@ -75,7 +75,7 @@ Before proceeding to merge: - [ ] All three remotes configured (origin, upstream, upstream-pwsh) - [ ] Can fetch from all remotes without errors - [ ] Can see upstream target version/branch -- [ ] Working directory is clean (`git status` shows no uncommitted changes) +- [ ] Working directory is clean (use Invoke-Git `Operation="Status"` — `ModifiedFiles` and `ConflictedFiles` should both be empty) ## Troubleshooting diff --git a/.github/tools/Invoke-Git.ps1 b/.github/tools/Invoke-Git.ps1 new file mode 100644 index 000000000000..f091e32b016c --- /dev/null +++ b/.github/tools/Invoke-Git.ps1 @@ -0,0 +1,406 @@ +<# +.SYNOPSIS + Executes a git operation and returns a structured result with exit code. + +.DESCRIPTION + MCP-compatible tool that runs git commands via System.Diagnostics.Process for + reliable exit code capture without interfering with the MCP server's stdio + transport. Async stream reads are started before WaitForExit to prevent deadlocks + when git output exceeds the stream buffer size. + + Returns a structured hashtable with Success, ExitCode, Output, Error, and + operation-specific fields so agents can make programmatic decisions without + text parsing. + +.PARAMETER Operation + The git operation to perform. One of: + CherryPick, CherryPickContinue, CherryPickAbort, + Add, Checkout, CreateBranch, + Commit, Push, Fetch, + Config, Reset, Clean, + Status, Log, Diff, Show + +.PARAMETER CommitHash + A commit SHA or any git ref (branch name, tag, etc.). + Used by: CherryPick, Show. + +.PARAMETER Range + A git range expression (e.g. "abc123^..def456" or "HEAD..upstream/V_10_0_P2"). + Used by: Log (and Log with ShasOnly), Diff. + +.PARAMETER Message + Commit message text. + Used by: Commit. + +.PARAMETER Path + File path or directory to operate on. Defaults to '.'. + Used by: Add, Checkout (file restore), Diff, Show. + +.PARAMETER Remote + Remote name. Defaults to 'origin'. + Used by: Fetch, Push. + +.PARAMETER Branch + Branch name to create or push. + Used by: CreateBranch, Push. + +.PARAMETER StartPoint + Git ref (branch, tag, or commit) to base the new branch on. + Used by: CreateBranch. + +.PARAMETER Target + Branch, tag, commit ref, or file path to check out or reset to. + Used by: Checkout, Reset. + +.PARAMETER Key + Git config key (e.g. "core.editor"). + Used by: Config. + +.PARAMETER Value + Git config value to set. + Used by: Config. + +.PARAMETER Mode + Reset mode: 'soft', 'mixed' (default), or 'hard'. + Used by: Reset. + +.PARAMETER ShasOnly + When specified alongside Operation=Log, uses git rev-list --reverse instead of + git log --oneline. Returns commit SHAs in oldest-first order suitable for building + a cherry-pick loop. result.Commits will contain [{Hash: "", Message: ""}]. + +.OUTPUTS + Hashtable with: + Success [bool] Whether the command exited with code 0 + ExitCode [int] Raw process exit code + Output [string] Stdout from git + Error [string] Stderr from git + Message [string] Human-readable summary + Plus operation-specific fields: + CherryPick (on failure): ConflictedFiles [string[]] + Log / ShasOnly: Commits [{Hash, Message}] + Status: ConflictedFiles [string[]], ModifiedFiles [string[]] + Commit (on success): CommitHash [string] + +.EXAMPLE + # Cherry-pick a single commit + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CherryPick", CommitHash="abc1234" + +.EXAMPLE + # Get ordered commit SHAs in a batch range for a cherry-pick loop + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Log", Range="abc123^..def456", ShasOnly=true + +.EXAMPLE + # Stage all changes after conflict resolution (Path defaults to '.') + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Add" + +.EXAMPLE + # Continue cherry-pick after resolving conflicts + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CherryPickContinue" + +.EXAMPLE + # Abort an in-progress cherry-pick + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CherryPickAbort" + +.EXAMPLE + # Restore paths.targets after a build modifies it + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Checkout", Target=".\contrib\win32\openssh\paths.targets" + +.EXAMPLE + # Create and check out a new merge branch + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CreateBranch", Branch="merge-v10.0P2-20260306" + +.EXAMPLE + # Commit staged changes + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Commit", Message="Fix compilation errors for V_10_0_P2" + +.EXAMPLE + # Push a branch to origin + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Push", Remote="origin", Branch="merge-v10.0P2-20260306" + +.EXAMPLE + # Set a git config value + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Config", Key="core.editor", Value="true" + +.EXAMPLE + # Show differences between two refs for a specific file + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Diff", Range="upstream-pwsh/latestw_all..upstream/V_10_0_P2", Path="Makefile.in" + +.EXAMPLE + # Inspect a specific commit + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Show", CommitHash="abc1234" + +.EXAMPLE + # Check working directory status for conflicts and modifications + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Status" + +.EXAMPLE + # Hard-reset to HEAD (recovery) + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Reset", Target="HEAD", Mode="hard" + +.EXAMPLE + # Remove untracked files (recovery) + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Clean" +#> + +param( + [Parameter(Mandatory)] + [ValidateSet( + 'CherryPick', 'CherryPickContinue', 'CherryPickAbort', + 'Add', 'Checkout', 'CreateBranch', + 'Commit', 'Push', 'Fetch', + 'Config', 'Reset', 'Clean', + 'Status', 'Log', 'Diff', 'Show' + )] + [string]$Operation, + + # CherryPick, Show — accepts any git ref: commit SHA, branch name, tag, etc. + [string]$CommitHash = '', + + # Log (plain or ShasOnly), Diff — git range expression e.g. "abc123^..def456" + [string]$Range = '', + + # Commit — commit message text + [string]$Message = '', + + # Add, Checkout (file restore), Diff, Show — file/directory path + [string]$Path = '.', + + # Fetch, Push — remote name + [string]$Remote = 'origin', + + # CreateBranch, Push — branch name + [string]$Branch = '', + + # CreateBranch — starting ref for the new branch + [string]$StartPoint = '', + + # Checkout, Reset — target ref or file path + [string]$Target = '', + + # Config — key (e.g. "core.editor") + [string]$Key = '', + + # Config — value to set + [string]$Value = '', + + # Reset — reset mode + [ValidateSet('soft', 'mixed', 'hard')] + [string]$Mode = 'mixed', + + # Log — use git rev-list --reverse (SHAs only, oldest first) instead of git log --oneline + [switch]$ShasOnly +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# --------------------------------------------------------------------------- +# Private helper — runs git via System.Diagnostics.Process. +# +# IMPORTANT: We use ProcessStartInfo rather than Start-Process because +# Start-Process with -RedirectStandardOutput/-RedirectStandardError conflicts +# with the MCP server's stdio transport, causing the process to hang. +# +# Async stream reads MUST be started BEFORE WaitForExit to prevent deadlocks: +# if git output exceeds the internal stream buffer, git blocks waiting for the +# buffer to drain while WaitForExit blocks waiting for git to exit. +# --------------------------------------------------------------------------- +function Invoke-GitCommand { + param([string[]]$Arguments) + + $processInfo = [System.Diagnostics.ProcessStartInfo]::new() + $processInfo.FileName = 'git' + $processInfo.Arguments = ($Arguments | ForEach-Object { + if ($_ -match '\s') { "`"$_`"" } else { $_ } + }) -join ' ' + $processInfo.UseShellExecute = $false + $processInfo.CreateNoWindow = $true + $processInfo.RedirectStandardInput = $true # Prevent git inheriting the MCP stdio pipe + $processInfo.RedirectStandardOutput = $true + $processInfo.RedirectStandardError = $true + + $process = [System.Diagnostics.Process]::new() + $process.StartInfo = $processInfo + $process.Start() | Out-Null + $process.StandardInput.Close() # Immediately close stdin so git never blocks on input + + # Begin async reads BEFORE blocking on WaitForExit + $stdoutTask = $process.StandardOutput.ReadToEndAsync() + $stderrTask = $process.StandardError.ReadToEndAsync() + + $completed = $process.WaitForExit(30000) # 30-second timeout + + if (-not $completed) { + $process.Kill() + return @{ + ExitCode = -1 + Success = $false + Output = '' + Error = "git $($Arguments -join ' ') timed out after 30 seconds" + } + } + + return @{ + ExitCode = $process.ExitCode + Success = ($process.ExitCode -eq 0) + Output = $stdoutTask.GetAwaiter().GetResult().TrimEnd() + Error = $stderrTask.GetAwaiter().GetResult().TrimEnd() + } +} + +# --------------------------------------------------------------------------- +# Operation dispatcher +# --------------------------------------------------------------------------- +$result = switch ($Operation) { + + 'CherryPick' { + if (-not $CommitHash) { throw 'CommitHash is required for CherryPick' } + $r = Invoke-GitCommand -Arguments @('cherry-pick', $CommitHash) + if (-not $r.Success) { + # Enrich failure result with list of conflicted files for agent reporting + $statusResult = Invoke-GitCommand -Arguments @('status', '--porcelain') + $r['ConflictedFiles'] = ($statusResult.Output -split "`n") | + Where-Object { $_ -match '^(UU|AA|DD|AU|UA|DU|UD)\s' } | + ForEach-Object { $_.Substring(3).Trim() } + } + $r + } + + 'CherryPickContinue' { + Invoke-GitCommand -Arguments @('cherry-pick', '--continue') + } + + 'CherryPickAbort' { + Invoke-GitCommand -Arguments @('cherry-pick', '--abort') + } + + 'Add' { + Invoke-GitCommand -Arguments @('add', $Path) + } + + 'Checkout' { + if (-not $Target) { throw 'Target is required for Checkout (branch name, tag, or file path)' } + Invoke-GitCommand -Arguments @('checkout', $Target) + } + + 'CreateBranch' { + if (-not $Branch) { throw 'Branch is required for CreateBranch' } + $args = @('checkout', '-b', $Branch) + if ($StartPoint) { $args += $StartPoint } + Invoke-GitCommand -Arguments $args + } + + 'Commit' { + if (-not $Message) { throw 'Message is required for Commit' } + $r = Invoke-GitCommand -Arguments @('commit', '-m', $Message) + if ($r.Success -and $r.Output -match '\[[\w/]+ ([0-9a-f]{7,})\]') { + $r['CommitHash'] = $Matches[1] + } + $r + } + + 'Push' { + $args = @('push', $Remote) + if ($Branch) { $args += $Branch } + Invoke-GitCommand -Arguments $args + } + + 'Fetch' { + $args = if ($Remote -eq 'all') { @('fetch', '--all') } else { @('fetch', $Remote) } + Invoke-GitCommand -Arguments $args + } + + 'Config' { + if (-not $Key) { throw 'Key is required for Config' } + $args = @('config', $Key) + if ($Value) { $args += $Value } + Invoke-GitCommand -Arguments $args + } + + 'Reset' { + if (-not $Target) { throw 'Target is required for Reset' } + Invoke-GitCommand -Arguments @('reset', "--$Mode", $Target) + } + + 'Clean' { + # -f (force) -d (include untracked directories) — standard recovery clean + Invoke-GitCommand -Arguments @('clean', '-fd') + } + + 'Status' { + $r = Invoke-GitCommand -Arguments @('status', '--porcelain') + $lines = if ($r.Output) { $r.Output -split "`n" } else { @() } + $r['ConflictedFiles'] = $lines | + Where-Object { $_ -match '^(UU|AA|DD|AU|UA|DU|UD)\s' } | + ForEach-Object { $_.Substring(3).Trim() } + $r['ModifiedFiles'] = $lines | + Where-Object { $_ -notmatch '^(UU|AA|DD|AU|UA|DU|UD)\s' -and $_ -match '^\S' } | + ForEach-Object { ($_ -replace '^\S+\s+', '').Trim() } + $r + } + + 'Log' { + if (-not $Range) { throw 'Range is required for Log (e.g. "abc123^..def456" or "HEAD..upstream/V_10_0_P2")' } + if ($ShasOnly) { + # Use rev-list --reverse to get SHAs in oldest-first (cherry-pick) order + $r = Invoke-GitCommand -Arguments @('rev-list', '--reverse', $Range) + $r['Commits'] = if ($r.Success -and $r.Output) { + ($r.Output -split "`n") | + Where-Object { $_ -match '^[0-9a-f]{7,}$' } | + ForEach-Object { @{ Hash = $_; Message = '' } } + } else { @() } + } else { + $logArgs = @('log', '--oneline', $Range) + if ($Path -and $Path -ne '.') { $logArgs += '--'; $logArgs += $Path } + $r = Invoke-GitCommand -Arguments $logArgs + $r['Commits'] = if ($r.Success -and $r.Output) { + ($r.Output -split "`n") | + Where-Object { $_ -match '^[0-9a-f]' } | + ForEach-Object { + if ($_ -match '^([0-9a-f]+)\s+(.+)$') { + @{ Hash = $Matches[1]; Message = $Matches[2] } + } + } + } else { @() } + } + $r + } + + 'Diff' { + $args = @('diff') + if ($Range) { $args += $Range } + if ($Path -and $Path -ne '.') { $args += '--'; $args += $Path } + Invoke-GitCommand -Arguments $args + } + + 'Show' { + $args = @('show') + if ($CommitHash) { $args += $CommitHash } + if ($Path -and $Path -ne '.') { $args += '--'; $args += $Path } + Invoke-GitCommand -Arguments $args + } +} + +$result['Message'] = if ($result.Success) { + "git $Operation completed successfully" +} else { + "git $Operation failed with exit code $($result.ExitCode): $($result.Error)" +} + +return $result diff --git a/.github/tools/Test-OpenSSHFunctionality.ps1 b/.github/tools/Test-OpenSSHFunctionality.ps1 index 9e1b0f7c8907..7a1186dc98a2 100644 --- a/.github/tools/Test-OpenSSHFunctionality.ps1 +++ b/.github/tools/Test-OpenSSHFunctionality.ps1 @@ -307,16 +307,20 @@ try { $process.Start() | Out-Null - # Wait for completion (with timeout) - $completed = $process.WaitForExit(15000) # 15 second timeout + # Begin async reads BEFORE blocking on WaitForExit — prevents deadlock when + # SSH output exceeds the internal stream buffer size (same issue as MCP stdio). + $stdoutTask = $process.StandardOutput.ReadToEndAsync() + $stderrTask = $process.StandardError.ReadToEndAsync() + + $completed = $process.WaitForExit(30000) # 30-second timeout if (-not $completed) { $process.Kill() - throw "SSH connection timed out after 15 seconds" + throw "SSH connection timed out after 30 seconds" } - $stdout = $process.StandardOutput.ReadToEnd() - $stderr = $process.StandardError.ReadToEnd() + $stdout = $stdoutTask.GetAwaiter().GetResult() + $stderr = $stderrTask.GetAwaiter().GetResult() $exitCode = $process.ExitCode # Check result From 8a16638903648b486037009322f613575fbb9f4e Mon Sep 17 00:00:00 2001 From: User Date: Fri, 6 Mar 2026 09:59:33 -0800 Subject: [PATCH 62/68] add tool to get context during complex conflicts --- .github/agents/merge-upstream.agent.md | 19 +- .../merge/merge-details.instructions.md | 38 ++ .github/tools/Get-ConflictContext.ps1 | 489 ++++++++++++++++++ 3 files changed, 544 insertions(+), 2 deletions(-) create mode 100644 .github/tools/Get-ConflictContext.ps1 diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index bb67a39cefbb..559261dfac96 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -2,7 +2,7 @@ name: merge-upstream description: Assist with merging upstream OpenSSH commits into the PowerShell fork. tools: - ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'openssh-server/*', 'todo'] + ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'agent', 'openssh-server/*', 'todo'] --- # OpenSSH Upstream Merge Agent @@ -53,7 +53,22 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - `NoCleanup` (boolean, optional): Skip cleanup for debugging (default: false) - **If tool unavailable**: ERROR - This tool is required for the merge workflow -4. **Git** - Version control operations +4. **Get-ConflictContext MCP Tool** - Three-way conflict context for high-complexity conflicts + - **MCP Tool Name**: `mcp_openssh-server_Get_ConflictContext` + - **When to use**: ONLY when `assess_conflict_complexity()` returns `HIGH_COMPLEXITY` + - **Parameters**: + - `FilePath` (string, required): Path to the conflicted file relative to the repository root + - `CommitHash` (string, required): The upstream commit SHA being cherry-picked that caused the conflict + - `ContextLines` (integer, optional): Lines of context above/below each hunk match (default: 40) + - `MaxTotalLines` (integer, optional): Maximum total lines across all three versions and all hunks (default: 150 — ~50 per version). Increase if broader context is needed. + - **What it returns**: For each hunk in the upstream diff — excerpts from three versions: + - `UpstreamBefore`: The file as it existed in upstream *before* this commit + - `UpstreamAfter`: The file in upstream *after* this commit + - `OurFork`: The corresponding region in our fork (located by content-anchor matching, not line numbers) + - **Budget**: `max(10, floor(MaxTotalLines / 3 / hunkCount))` lines per version per hunk; a warning is added to `Message` if the 10-line minimum floor is applied + - **If tool unavailable**: Fall back to reading the conflicted file directly and using `Invoke_Git Operation="Show"` and `Operation="Diff"` to gather context manually + +5. **Git** - Version control operations - Cherry-pick: `git cherry-pick start_commit^..end_commit` (inclusive) - Status: `git status` - Remotes: `origin`, `upstream-pwsh`, `upstream` diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md index 875171e54b27..aaeab4fc3bb2 100644 --- a/.github/instructions/merge/merge-details.instructions.md +++ b/.github/instructions/merge/merge-details.instructions.md @@ -418,6 +418,44 @@ FUNCTION assess_conflict_complexity(conflicts): RETURN "LOW_COMPLEXITY" ``` +### Using Get-ConflictContext for High-Complexity Conflicts + +When `assess_conflict_complexity()` returns `HIGH_COMPLEXITY`, invoke the `Get-ConflictContext` MCP tool **before** attempting to edit the file. It provides three-way context anchored to the actual changed regions, accounting for the fact that our fork's line numbers differ from upstream. + +```pseudocode +FUNCTION resolve_conflict(file_path, conflict_content, cherry_pick_commit): + complexity = assess_conflict_complexity([{file: file_path, content: conflict_content}]) + + IF complexity == "HIGH_COMPLEXITY": + // Fetch three-way context before editing + // MCP Tool: mcp_openssh-server_Get_ConflictContext + // FilePath=file_path, CommitHash=cherry_pick_commit + // + // If the default MaxTotalLines=150 is insufficient (e.g., many hunks or + // a large function), re-invoke with a higher value such as MaxTotalLines=300. + context = get_conflict_context(file_path, cherry_pick_commit) + + FOR EACH hunk IN context.Hunks: + // Use all three excerpts to understand: + // hunk.UpstreamBefore — what the upstream code looked like before the commit + // hunk.UpstreamAfter — what the upstream commit changed it to + // hunk.OurFork — what our fork has in the corresponding region + // (Note field explains how the region was located) + determine_resolution_strategy(hunk) + + // Check Message for budget warnings and increase MaxTotalLines if needed + IF context.Message contains "minimum floor": + re_invoke_with_larger_budget(file_path, cherry_pick_commit) + + RETURN apply_resolution_strategy(file_path, conflict_content) +``` + +**Key behaviours of the tool:** +- **Binary files**: Returns `IsBinary=true` and no excerpts — resolve manually. +- **Unavailable versions**: A version returns `Lines=null` with a `Note` explaining why (e.g. file newly added by this commit). +- **Fork region location**: Uses sliding-window content matching against the diff's unchanged context lines. Falls back to the function name from the `@@` hunk header if the anchor score is too low. The `Note` field on each `OurFork` excerpt describes which strategy was used. +- **Budget warning**: If `MaxTotalLines` is too small to give each hunk 10 lines per version, a warning appears in `Message` — increase `MaxTotalLines` and re-invoke. + ## Anti-Patterns to Avoid ### ❌ Don't Remove Upstream Code diff --git a/.github/tools/Get-ConflictContext.ps1 b/.github/tools/Get-ConflictContext.ps1 new file mode 100644 index 000000000000..e83d6a4c4fd3 --- /dev/null +++ b/.github/tools/Get-ConflictContext.ps1 @@ -0,0 +1,489 @@ +<# +.SYNOPSIS + Retrieves three-way context for a conflicted file to aid complex conflict resolution. + +.DESCRIPTION + MCP-compatible tool that fetches three versions of a file involved in a cherry-pick + conflict — the upstream file before the commit, the upstream file after the commit, + and our fork's version (via MERGE_HEAD) — and extracts focused, line-numbered excerpts + centered on each changed hunk. + + For each hunk in the upstream diff, the tool locates the corresponding region in our + fork using two-tier content matching: + Tier 1: Sliding-window overlap score against unchanged context lines from the diff. + Tier 2: Fallback to searching for the function name from the @@ hunk header. + + This handles line-number divergence between upstream and our fork by finding the + region by content rather than by position. + + Use this tool ONLY when conflict complexity assessment returns HIGH_COMPLEXITY. + +.PARAMETER FilePath + Path to the conflicted file, relative to the repository root. + +.PARAMETER CommitHash + The upstream commit SHA being cherry-picked that caused the conflict. + +.PARAMETER ContextLines + Number of lines of context above and below each hunk match to include in excerpts. + Default: 40 + +.PARAMETER MaxTotalLines + Maximum total lines returned across all three versions combined (across all hunks). + Budget per version per hunk = max(10, floor(MaxTotalLines / 3 / hunkCount)). + The minimum floor of 10 lines per version per hunk is always enforced. + If the floor overrides the calculated budget, a warning is included in Message. + Default: 150 (approximately 50 lines per version) + +.OUTPUTS + Hashtable with: + Success [bool] Whether the operation completed without errors + Message [string] Human-readable summary (includes warnings about budget) + CommitMessage [string] The commit message of CommitHash + UpstreamDiff [string] Raw unified diff for the file from this commit + HunkCount [int] Number of hunks found in the upstream diff + IsBinary [bool] True if the file is binary (no excerpts returned) + Hunks [array] One entry per hunk: + HunkIndex [int] 1-based hunk number + HunkHeader [string] The @@ header line + FunctionName [string] Function name extracted from @@ header (may be empty) + UpstreamBefore [object] { Lines, StartLine, EndLine, Note } + UpstreamAfter [object] { Lines, StartLine, EndLine, Note } + OurFork [object] { Lines, StartLine, EndLine, Note } + + Each excerpt object: + Lines [string[]] The extracted lines (null if version unavailable) + StartLine [int] 1-based line number of first line in the excerpt + EndLine [int] 1-based line number of last line in the excerpt + Note [string] Explanation if unavailable or how region was located + +.EXAMPLE + # Get conflict context for a high-complexity conflict + # MCP Tool: mcp_openssh-server_Get_ConflictContext + # FilePath="auth.c", CommitHash="abc1234" + +.EXAMPLE + # Get context with increased budget for a file with many hunks + # MCP Tool: mcp_openssh-server_Get_ConflictContext + # FilePath="channels.c", CommitHash="abc1234", MaxTotalLines=300 +#> + +param( + [Parameter(Mandatory)] + [string]$FilePath, + + [Parameter(Mandatory)] + [string]$CommitHash, + + [int]$ContextLines = 40, + + [int]$MaxTotalLines = 150 +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# ── Git process helper (same pattern as Invoke-Git.ps1) ────────────────────── + +function Invoke-GitCommand { + param([string[]]$Arguments) + + $processInfo = [System.Diagnostics.ProcessStartInfo]::new() + $processInfo.FileName = 'git' + $processInfo.Arguments = ($Arguments | ForEach-Object { + if ($_ -match '\s') { "`"$_`"" } else { $_ } + }) -join ' ' + $processInfo.UseShellExecute = $false + $processInfo.CreateNoWindow = $true + $processInfo.RedirectStandardInput = $true + $processInfo.RedirectStandardOutput = $true + $processInfo.RedirectStandardError = $true + + $process = [System.Diagnostics.Process]::new() + $process.StartInfo = $processInfo + $process.Start() | Out-Null + $process.StandardInput.Close() + + $stdoutTask = $process.StandardOutput.ReadToEndAsync() + $stderrTask = $process.StandardError.ReadToEndAsync() + + $completed = $process.WaitForExit(30000) + + if (-not $completed) { + $process.Kill() + return @{ + ExitCode = -1 + Success = $false + Output = '' + Error = "git $($Arguments -join ' ') timed out after 30 seconds" + } + } + + return @{ + ExitCode = $process.ExitCode + Success = ($process.ExitCode -eq 0) + Output = $stdoutTask.GetAwaiter().GetResult() + Error = $stderrTask.GetAwaiter().GetResult().TrimEnd() + } +} + +# ── Helper: fetch a file at a git ref ──────────────────────────────────────── + +function Get-FileAtRef { + param([string]$Ref, [string]$File) + + $r = Invoke-GitCommand -Arguments @('show', "${Ref}:${File}") + if (-not $r.Success) { + return @{ Lines = $null; Note = "File not available at ref '${Ref}': $($r.Error.Trim())" } + } + $lines = $r.Output -split "`n" + # Remove trailing empty element produced by split on a newline-terminated string + if ($lines.Count -gt 0 -and $lines[-1] -eq '') { + $lines = $lines[0..($lines.Count - 2)] + } + return @{ Lines = $lines; Note = $null } +} + +# ── Helper: slice a line-numbered excerpt centred on a 1-based line ────────── + +function Get-Excerpt { + param( + [string[]]$Lines, + [int]$CenterLine, # 1-based + [int]$Budget # max lines to return + ) + + if (-not $Lines) { return $null } + $half = [Math]::Floor($Budget / 2) + $start = [Math]::Max(0, $CenterLine - 1 - $half) + $end = [Math]::Min($Lines.Count - 1, $CenterLine - 1 + $half) + + # Expand toward the opposite edge if we hit a boundary before using the full budget + if (($end - $start + 1) -lt $Budget) { + if ($start -eq 0) { + $end = [Math]::Min($Lines.Count - 1, $Budget - 1) + } else { + $start = [Math]::Max(0, $end - $Budget + 1) + } + } + + return @{ + Lines = $Lines[$start..$end] + StartLine = $start + 1 + EndLine = $end + 1 + Note = $null + } +} + +# ── Helper: sliding-window content-anchor match ─────────────────────────────── +# Returns the 1-based centre line in $FileLines that best overlaps $AnchorLines. + +function Find-AnchorMatch { + param( + [string[]]$FileLines, + [string[]]$AnchorLines, + [int]$ExpectedCenter # 1-based fallback if no match found + ) + + $anchorSet = @{} + foreach ($a in $AnchorLines) { + $t = $a.Trim() + if ($t) { $anchorSet[$t] = $true } + } + + $windowSize = [Math]::Max($AnchorLines.Count, 5) + $bestScore = -1 + $bestCenter = $ExpectedCenter + + for ($i = 0; $i -le ($FileLines.Count - $windowSize); $i++) { + $score = 0 + for ($j = $i; $j -lt ($i + $windowSize) -and $j -lt $FileLines.Count; $j++) { + $trimmed = $FileLines[$j].Trim() + if ($trimmed -and $anchorSet.ContainsKey($trimmed)) { $score++ } + } + if ($score -gt $bestScore) { + $bestScore = $score + $bestCenter = $i + [Math]::Floor($windowSize / 2) + 1 # convert to 1-based + } + } + + return @{ + Center = $bestCenter + Score = $bestScore + MaxPossible = $anchorSet.Count + } +} + +# ── Helper: find the first line in $FileLines containing $FunctionName ─────── + +function Find-FunctionMatch { + param( + [string[]]$FileLines, + [string]$FunctionName + ) + + $pattern = "\b$([regex]::Escape($FunctionName))\b" + for ($i = 0; $i -lt $FileLines.Count; $i++) { + if ($FileLines[$i] -match $pattern) { + return $i + 1 # 1-based + } + } + return -1 +} + +# ── Helper: parse all @@ hunks from unified diff text ──────────────────────── + +function Parse-DiffHunks { + param([string]$DiffText) + + $hunks = @() + $lines = $DiffText -split "`n" + $currentHunk = $null + $contextAccum = @() + $inHunk = $false + + foreach ($line in $lines) { + if ($line -match '^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$') { + + # Flush the previous hunk before starting a new one + if ($null -ne $currentHunk) { + $currentHunk['ContextLines'] = $contextAccum + $hunks += $currentHunk + } + + $upstreamStart = [int]$Matches[1] + $upstreamCount = if ($Matches[2]) { [int]$Matches[2] } else { 1 } + $afterStart = [int]$Matches[3] + $afterCount = if ($Matches[4]) { [int]$Matches[4] } else { 1 } + $funcTrailer = $Matches[5].Trim() + + # Extract function name from the optional trailer after @@ + $funcName = '' + if ($funcTrailer -match '(\w[\w_]*)\s*\(') { + $funcName = $Matches[1] + } elseif ($funcTrailer -match '(\w[\w_]+)') { + $funcName = $Matches[1] + } + + $currentHunk = @{ + Header = $line.Trim() + FunctionName = $funcName + UpstreamStart = $upstreamStart + UpstreamCount = $upstreamCount + AfterStart = $afterStart + AfterCount = $afterCount + } + $contextAccum = @() + $inHunk = $true + + } elseif ($inHunk) { + # Collect unchanged context lines (lines starting with a space) + if ($line -match '^ (.*)$') { + $contextAccum += $Matches[1] + } + } + } + + # Flush the last hunk + if ($null -ne $currentHunk) { + $currentHunk['ContextLines'] = $contextAccum + $hunks += $currentHunk + } + + return $hunks +} + +# ── Main ────────────────────────────────────────────────────────────────────── + +$warnings = @() + +# 1. Get the upstream diff for this file at this commit +$diffResult = Invoke-GitCommand -Arguments @('diff', "${CommitHash}^..${CommitHash}", '--', $FilePath) +if (-not $diffResult.Success -and $diffResult.ExitCode -ne 1) { + return @{ + Success = $false + Message = "Failed to get diff for '${FilePath}' at commit ${CommitHash}: $($diffResult.Error)" + CommitMessage = '' + UpstreamDiff = '' + HunkCount = 0 + IsBinary = $false + Hunks = @() + } +} + +$upstreamDiff = $diffResult.Output + +# 2. Binary file — return early with a note, no excerpts +if ($upstreamDiff -match 'Binary files .* differ') { + return @{ + Success = $true + Message = "Binary file — context not available for '${FilePath}'." + CommitMessage = '' + UpstreamDiff = $upstreamDiff + HunkCount = 0 + IsBinary = $true + Hunks = @() + } +} + +# 3. File not touched by this commit +if ([string]::IsNullOrWhiteSpace($upstreamDiff)) { + return @{ + Success = $true + Message = "File '${FilePath}' was not modified by commit ${CommitHash}." + CommitMessage = '' + UpstreamDiff = '' + HunkCount = 0 + IsBinary = $false + Hunks = @() + } +} + +# 4. Get the commit message +$commitMsgResult = Invoke-GitCommand -Arguments @('log', '-1', '--pretty=format:%s%n%n%b', $CommitHash) +$commitMessage = if ($commitMsgResult.Success) { $commitMsgResult.Output.Trim() } else { '' } + +# 5. Fetch the three file versions +# MERGE_HEAD is populated by git during an in-progress cherry-pick conflict +# and points to the commit being cherry-picked (our fork's working state before merge) +$upstreamBefore = Get-FileAtRef -Ref "${CommitHash}^" -File $FilePath +$upstreamAfter = Get-FileAtRef -Ref "${CommitHash}" -File $FilePath +$ourFork = Get-FileAtRef -Ref 'MERGE_HEAD' -File $FilePath + +# 6. Parse hunks from the diff +$hunks = Parse-DiffHunks -DiffText $upstreamDiff +$hunkCount = $hunks.Count + +if ($hunkCount -eq 0) { + return @{ + Success = $true + Message = "No hunks found in diff for '${FilePath}' at commit ${CommitHash}." + CommitMessage = $commitMessage + UpstreamDiff = $upstreamDiff + HunkCount = 0 + IsBinary = $false + Hunks = @() + } +} + +# 7. Compute per-hunk line budget +# MaxTotalLines is split evenly across 3 versions and all hunks. +# Minimum floor of 10 lines per version per hunk is always enforced. +$MIN_LINES_PER_HUNK = 10 +$budgetPerHunk = [Math]::Floor($MaxTotalLines / 3 / $hunkCount) + +if ($budgetPerHunk -lt $MIN_LINES_PER_HUNK) { + $warnings += "MaxTotalLines=${MaxTotalLines} is too small for ${hunkCount} hunk(s) across 3 versions; " + + "minimum floor of ${MIN_LINES_PER_HUNK} lines applied — consider increasing MaxTotalLines." + $budgetPerHunk = $MIN_LINES_PER_HUNK +} + +# Never exceed the caller's ContextLines preference +$budgetPerHunk = [Math]::Min($budgetPerHunk, $ContextLines) + +# 8. Build per-hunk results +$ANCHOR_SCORE_THRESHOLD = 2 +$resultHunks = @() + +for ($h = 0; $h -lt $hunkCount; $h++) { + + $hunk = $hunks[$h] + $anchorLines = $hunk['ContextLines'] + + # ── upstream-before: line numbers are known from the diff header ────────── + $uBefore = @{ Lines = $null; StartLine = $null; EndLine = $null; Note = $upstreamBefore.Note } + if ($upstreamBefore.Lines) { + $center = $hunk['UpstreamStart'] + [Math]::Floor($hunk['UpstreamCount'] / 2) + $excerpt = Get-Excerpt -Lines $upstreamBefore.Lines -CenterLine $center -Budget $budgetPerHunk + if ($excerpt) { $uBefore = $excerpt } + } + + # ── upstream-after: line numbers are known from the diff header ─────────── + $uAfter = @{ Lines = $null; StartLine = $null; EndLine = $null; Note = $upstreamAfter.Note } + if ($upstreamAfter.Lines) { + $center = $hunk['AfterStart'] + [Math]::Floor($hunk['AfterCount'] / 2) + $excerpt = Get-Excerpt -Lines $upstreamAfter.Lines -CenterLine $center -Budget $budgetPerHunk + if ($excerpt) { $uAfter = $excerpt } + } + + # ── our fork: line numbers diverge — locate by content matching ────────── + $forkExcerpt = @{ Lines = $null; StartLine = $null; EndLine = $null; Note = $ourFork.Note } + + if ($ourFork.Lines) { + $forkCenter = $hunk['UpstreamStart'] # fallback: use upstream line number as estimate + $matchNote = $null + + if ($anchorLines.Count -ge 2) { + # Tier 1: sliding-window content match against unchanged context lines + $match = Find-AnchorMatch -FileLines $ourFork.Lines ` + -AnchorLines $anchorLines ` + -ExpectedCenter $forkCenter + + if ($match.Score -ge $ANCHOR_SCORE_THRESHOLD) { + $forkCenter = $match.Center + $matchNote = "Located via content-anchor match (score $($match.Score)/$($match.MaxPossible))." + } elseif ($hunk['FunctionName']) { + # Tier 2: anchor score too low — fall back to function name search + $fnLine = Find-FunctionMatch -FileLines $ourFork.Lines -FunctionName $hunk['FunctionName'] + if ($fnLine -gt 0) { + $forkCenter = $fnLine + $matchNote = "Located via function-name fallback ('$($hunk['FunctionName'])')." + } else { + $matchNote = "Could not locate region — anchor score too low and function " + + "'$($hunk['FunctionName'])' not found. Using upstream line number as estimate." + } + } else { + $matchNote = "Could not locate region — anchor score too low and no function name " + + "available. Using upstream line number as estimate." + } + + } elseif ($hunk['FunctionName']) { + # Too few anchor lines for sliding window — go straight to function-name search + $fnLine = Find-FunctionMatch -FileLines $ourFork.Lines -FunctionName $hunk['FunctionName'] + if ($fnLine -gt 0) { + $forkCenter = $fnLine + $matchNote = "Located via function-name fallback ('$($hunk['FunctionName'])') — " + + "insufficient anchor lines for content match." + } else { + $matchNote = "Insufficient anchor lines and function '$($hunk['FunctionName'])' not found. " + + "Using upstream line number as estimate." + } + } else { + $matchNote = "Insufficient anchor lines and no function name available. " + + "Using upstream line number as estimate." + } + + $excerpt = Get-Excerpt -Lines $ourFork.Lines -CenterLine $forkCenter -Budget $budgetPerHunk + if ($excerpt) { + $excerpt['Note'] = $matchNote + $forkExcerpt = $excerpt + } + } + + $resultHunks += @{ + HunkIndex = $h + 1 + HunkHeader = $hunk['Header'] + FunctionName = $hunk['FunctionName'] + UpstreamBefore = $uBefore + UpstreamAfter = $uAfter + OurFork = $forkExcerpt + } +} + +# 9. Return final result +$message = if ($warnings.Count -gt 0) { + "Context retrieved for ${hunkCount} hunk(s) in '${FilePath}' (commit ${CommitHash}). " + + "Warnings: $($warnings -join ' | ')" +} else { + "Context retrieved for ${hunkCount} hunk(s) in '${FilePath}' (commit ${CommitHash})." +} + +return @{ + Success = $true + Message = $message + CommitMessage = $commitMessage + UpstreamDiff = $upstreamDiff + HunkCount = $hunkCount + IsBinary = $false + Hunks = $resultHunks +} From 5f24965d20ff8e4bd89fff3fd744ca2c6390ecda Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Tue, 17 Mar 2026 16:57:03 -0400 Subject: [PATCH 63/68] add scratch branch instructions --- .github/agents/merge-upstream.agent.md | 206 ++++++++++++++---- .../agent-communication-merge.instructions.md | 29 +++ .../merge/merge-details.instructions.md | 38 +++- .../merge-process-overview.instructions.md | 121 ++++++---- .github/prompt/merge.prompt.md | 2 +- .github/tools/Get-ConflictContext.ps1 | 17 +- .github/tools/Invoke-Git.ps1 | 47 +++- .github/tools/Replay-MergeResolutions.ps1 | 201 +++++++++++++++++ .github/tools/Save-MergeResolution.ps1 | 160 ++++++++++++++ 9 files changed, 716 insertions(+), 105 deletions(-) create mode 100644 .github/tools/Replay-MergeResolutions.ps1 create mode 100644 .github/tools/Save-MergeResolution.ps1 diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index 559261dfac96..5bfcdd7deea4 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -16,7 +16,7 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for ### Core Functions - **Environment verification and setup** - **Commit group analysis** using Get-CommitGroups MCP tool -- **Incremental cherry-picking** with conflict resolution +- **Two-phase merge**: scratch branch (incremental merge + resolution recording) then real branch (single merge + replay) - **Windows-specific build system updates** - **Compilation and testing** - **Documentation and PR preparation** @@ -68,8 +68,30 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - **Budget**: `max(10, floor(MaxTotalLines / 3 / hunkCount))` lines per version per hunk; a warning is added to `Message` if the 10-line minimum floor is applied - **If tool unavailable**: Fall back to reading the conflicted file directly and using `Invoke_Git Operation="Show"` and `Operation="Diff"` to gather context manually -5. **Git** - Version control operations - - Cherry-pick: `git cherry-pick start_commit^..end_commit` (inclusive) +5. **Save-MergeResolution MCP Tool** - Records conflict resolution decisions + - **MCP Tool Name**: `mcp_openssh-server_Save_MergeResolution` + - **When to use**: After resolving each conflicted file during the scratch-branch phase + - **Parameters**: + - `FilePath` (string, required): Resolved file path relative to repo root + - `Strategy` (string, required): One of `accept_upstream`, `ifdef_windows`, `ifndef_windows`, `combine`, `manual` + - `Rationale` (string, required): Why this strategy was chosen + - `BatchNumber` (int, required): Current batch number + - `UpstreamCommits` (string, optional): Comma-separated SHAs of upstream commits touching this file + - `MergeTarget` (string, optional): Final upstream ref (only needed on first call to initialise the log) + - **If tool unavailable**: Agent should manually track resolutions in session memory + +6. **Replay-MergeResolutions MCP Tool** - Replays saved resolutions onto merge conflicts + - **MCP Tool Name**: `mcp_openssh-server_Replay_MergeResolutions` + - **When to use**: During the real-branch phase after `git merge` produces conflicts + - **Parameters**: + - `DryRun` (boolean, optional): Preview without modifying files (default: false) + - **Returns**: `ResolvedFiles[]`, `UnmatchedFiles[]`, `FailedFiles[]` + - **If tool unavailable**: Agent should manually re-resolve using strategies from session memory + +7. **Git** - Version control operations + - Merge: `Invoke-Git Operation="Merge" CommitHash=""` (uses `--no-ff`) + - MergeContinue / MergeAbort for conflict flow + - Cherry-pick operations remain available for other use cases - Status: `git status` - Remotes: `origin`, `upstream-pwsh`, `upstream` @@ -132,20 +154,36 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - This baseline will be used to detect new warnings introduced during merge - Store baseline for comparison after each build -4. **Create merge branch:** +4. **Enable git rerere** (records conflict resolutions for automatic replay): + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Config", Key="rerere.enabled", Value="true" + ``` + +5. **Create merge branch and scratch branch:** ```pwsh - git checkout -b merge-v- - # Example: git checkout -b merge-v10.0P2-20260109 + # Create the real merge branch (will receive the final single merge) + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CreateBranch", Branch="merge-v-" + # Example: Branch="merge-v10.0P2-20260109" + + # Create the scratch branch from the same point (for incremental merges) + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CreateBranch", Branch="scratch-merge-v-" ``` **Success Criteria:** - Prerequisite tool reports all checks passed - Baseline warning count established and documented -- Merge branch created -- Ready to begin Phase 2 (cherry-picking first batch) +- `rerere.enabled` set to `true` +- Both merge branch and scratch branch created +- Currently on the scratch branch +- Ready to begin Phase 2 (scratch-branch incremental merge) -### Phase 2: Incremental Merge -**Objective:** Cherry-pick commits in a single batch ending with a CI run +### Phase 2: Scratch Branch — Incremental Merge +**Objective:** Merge commits in batches on the scratch branch, recording every conflict resolution for later replay. + +The scratch branch uses `git merge` (not cherry-pick) at each batch boundary. This ensures conflict markers match the final single merge, maximising `git rerere` hit rate. **Steps:** 1. **Get first commit batch** using Get-CommitGroups MCP tool: @@ -169,24 +207,35 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for } ``` -2. **Display batch information for verification:** +2. **Display batch information** for verification. + +3. **Merge the batch endpoint:** ```pwsh - Write-Host "Processing Batch $($result.ChunkNumber)" -ForegroundColor Cyan - Write-Host "Commit Range: $($result.StartCommit)..$($result.EndCommit)" -ForegroundColor Gray - Write-Host "Start: [$($result.StartCommit)] $($result.StartMessage)" -ForegroundColor White - Write-Host "End: [$($result.EndCommit)] $($result.EndMessage)" -ForegroundColor Green - Write-Host "Total commits: $($result.CommitCount)" -ForegroundColor Yellow + # Merge all commits up to the batch endpoint + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Merge", CommitHash=$result.EndCommitFull ``` - -3. **Cherry-pick the batch:** + This brings in all commits from the previous merge point through `EndCommitFull` in a single merge. The `--no-ff` flag ensures a merge commit is always created. + +4. **If conflicts occur, resolve and record each one:** + - For each conflicted file reported in the merge result's `ConflictedFiles`: + a. Resolve the conflict following Windows preservation patterns + b. **Record the resolution** using Save-MergeResolution: + ```pwsh + # MCP Tool: mcp_openssh-server_Save_MergeResolution + # FilePath="", Strategy="", Rationale="", + # BatchNumber=, UpstreamCommits="" + # (On first call, also set MergeTarget="upstream/") + ``` + c. Stage the resolved file: `Invoke-Git Operation="Add" Path=""` + - `git rerere` will also automatically record the resolution. + +5. **Complete the merge:** ```pwsh - git cherry-pick $($result.StartCommitFull)^..$($result.EndCommitFull) + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="MergeContinue" ``` -4. **Resolve conflicts** following Windows preservation patterns (if conflicts occur) -5. **Stage and continue:** `git add .` then `git cherry-pick --continue` -6. **Repeat steps 4-5** until all commits in batch are applied - **Conflict Resolution Patterns:** - **Windows-specific code:** Preserve with `#ifdef WINDOWS` - **Removed featureand Validation @@ -279,38 +328,98 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - Summary provided with all required information - User approval received to proceed -### Phase 5: Iteration -**Objective:** Process remaining commit groups until merge is complete +### Phase 5: Scratch Branch Iteration +**Objective:** Process remaining commit batches on the scratch branch until all upstream commits are merged. **Steps:** 1. **Return to Phase 2** with `-StartCommit` set to previous batch's end commit and `-EndCommit` set to target end commit 2. **Repeat Phases 2-4** for each batch: - Get next batch with Get-CommitGroups (passing both StartCommit and EndCommit) - - Cherry-pick commits + - Merge batch endpoint (`Invoke-Git Merge`) + - Resolve conflicts and record with Save-MergeResolution - Build (mandatory) - Validate (if batch ends with successful CI) - Summarize and get approval 3. **Continue** until the target end commit is reached (or HEAD if no end commit was specified) -4. **Perform final comprehensive validation:** +4. **Final scratch-branch validation:** - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - **Parameters**: (use defaults for Release/x64) **Success Criteria:** -- All commit batches processed +- All commit batches processed on scratch branch - Build remains stable after each batch - All successful CI checkpoints validated -- Final validation passes -1. Use Get-CommitGroups MCP tool with `-StartCommit` parameter set to previous batch end commit -2. Repeat Phase 2-4 for next batch -3. Continue until all target commits merged -4. Perform final comprehensive test +- Resolution log (`.git/merge-resolution-log.json`) contains all conflict resolutions +- Ready to proceed to real-branch single merge + +### Phase 6: Real Branch — Single Merge +**Objective:** Produce clean history on the real merge branch with a single merge commit preserving all upstream SHAs. + +**Steps:** +1. **Switch to the real merge branch:** + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Checkout", Target="merge-v-" + ``` + +2. **Perform a single merge** of the final upstream target: + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Merge", CommitHash="" + ``` + This creates one merge commit. `git rerere` will automatically apply resolutions it recorded during the scratch phase. + +3. **Replay remaining resolutions** from the log: + ```pwsh + # MCP Tool: mcp_openssh-server_Replay_MergeResolutions + # (no parameters needed — reads from .git/merge-resolution-log.json) + ``` + The tool reports: + - `ResolvedFiles`: Files auto-resolved from the log + - `UnmatchedFiles`: Conflicted files not in the log (resolve manually) + - `FailedFiles`: Files where replay failed (resolve manually) + +4. **Resolve any remaining unmatched conflicts:** + - Use the resolution log's strategies and rationale as guidance + - These are typically files where the merge produced different conflict regions than the scratch-branch merge + +5. **Complete the merge:** + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="MergeContinue" + ``` + +6. **Apply build fixes** as separate commits after the merge commit: + - Review build fixes from the scratch branch + - Apply the same fixes (config.h.vs updates, .vcxproj changes, etc.) + - Commit with descriptive messages + - **CRITICAL: Restore paths.targets before committing:** + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Checkout", Target=".\contrib\win32\openssh\paths.targets" + ``` + +7. **Build and validate on the real branch:** + - Build: `mcp_openssh-server_Start_OpenSSHBuild` (Release/x64) + - Check warnings: `mcp_openssh-server_Test_OpenSSHBuild` + - Validate: `mcp_openssh-server_Test_OpenSSHFunctionality` + +8. **Clean up scratch branch:** + ```pwsh + # The scratch branch is no longer needed + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Checkout", Target="merge-v-" + # Then delete scratch: git branch -D scratch-merge-v- + ``` **Success Criteria:** -- All commit groups processed -- Build remains stable after each batch -- Tests pass after each batch +- Real branch has exactly one merge commit (plus build fix commits) +- All upstream commits appear in the DAG with original SHAs +- `git log --first-parent` shows a clean merge history +- Build succeeds and functionality tests pass +- Scratch branch deleted -### Phase 6: Documentation and PR +### Phase 7: Documentation and PR **Objective:** Document changes and submit for review **Steps:** @@ -390,18 +499,35 @@ This PR merges upstream OpenSSH commits from through . ## Recovery Procedures -### Abort Cherry-Pick +### Abort Merge +```bash +git merge --abort +git clean -fd +git reset --hard +``` + +### Abort Cherry-Pick (if used outside merge workflow) ```bash git cherry-pick --abort git clean -fd git reset --hard ``` +### Restart Scratch Branch +```bash +# Delete the scratch branch and recreate from the merge branch +git checkout merge-v- +git branch -D scratch-merge-v- +git checkout -b scratch-merge-v- +# Re-enable rerere if needed +git config rerere.enabled true +``` + ### Restart from Checkpoint ```bash git checkout merge-v- -git log --oneline -5 # Verify last successful commit -# Continue from there +git log --oneline -5 # Verify last successful state +# Continue from there (or recreate scratch branch) ``` ### Build Failure Recovery diff --git a/.github/instructions/merge/agent-communication-merge.instructions.md b/.github/instructions/merge/agent-communication-merge.instructions.md index 27c49018c943..80aa273e2ab9 100644 --- a/.github/instructions/merge/agent-communication-merge.instructions.md +++ b/.github/instructions/merge/agent-communication-merge.instructions.md @@ -301,6 +301,35 @@ Finalizing the merge: Ready to commit and push. ``` +### Scratch-to-Real Branch Transition: +``` +Scratch branch phase complete! + +Summary: +- Processed [N] batches on scratch branch +- Resolved [X] total conflicts across [Y] files +- All [N] batches built successfully +- Resolution log saved with [X] entries + +Switching to the real merge branch for the single final merge. +Recorded resolutions will be replayed automatically via git rerere and the resolution log. +``` + +### Real Branch Replay Report: +``` +Single merge completed on real branch. Resolution replay results: + +- Auto-resolved by git rerere: [X] files +- Replayed from resolution log: [Y] files +- Unmatched (manual resolution needed): [Z] files + - [file1]: [brief reason] + - [file2]: [brief reason] +- Failed replays: [W] files + +[If unmatched > 0]: Resolving the remaining [Z] files using strategies from the scratch phase... +[If all resolved]: All conflicts resolved. Applying build fixes as separate commits. +``` + ## Special Scenarios ### When Seeking User Approval: diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md index aaeab4fc3bb2..a01f5cd605b4 100644 --- a/.github/instructions/merge/merge-details.instructions.md +++ b/.github/instructions/merge/merge-details.instructions.md @@ -7,16 +7,20 @@ applyTo: "**/*" ## Overview This AI-specific documentation provides comprehensive instructions and algorithmic frameworks that AI agents can use to systematically approach the OpenSSH merge process with minimal human intervention while maintaining high quality and consistency. It combines conflict resolution strategies with automated decision-making processes. -**Key Approach: Chunked Merging** -Instead of merging entire upstream versions at once, this framework implements a chunked approach that: -- Processes commits in small batches ending with commits that have any CI run (successful or not) -- Groups commits by CI presence rather than CI success for more frequent checkpoints -- Builds after each batch (mandatory) +**Key Approach: Two-Phase Merge with Scratch Branch** +Instead of cherry-picking commits (which rewrites history), this framework implements a two-phase approach: + +1. **Scratch branch** — Incremental `git merge` at batch boundaries (grouped by CI presence). Build and test after each batch. Every conflict resolution is recorded via `git rerere` and the Save-MergeResolution MCP tool. +2. **Real branch** — A single `git merge` of the final upstream target. Recorded resolutions replay automatically via `git rerere` and Replay-MergeResolutions. This produces one merge commit with all upstream SHAs intact. + +Benefits: +- Preserves upstream commit history exactly (original SHAs, authors, timestamps) +- Uses incremental merge on scratch branch so conflict markers match the final merge (maximising `rerere` replay) +- Builds after each batch on scratch branch (mandatory) for early error detection - Validates functionality only at successful CI checkpoints - Requires user approval before proceeding to next batch - Allows for incremental progress and easier rollback - Reduces complexity of conflict resolution -- Enables better testing and validation at each step ## Decision Framework for AI Agents @@ -270,7 +274,7 @@ FUNCTION automated_build_fix(): // ALWAYS invoke Test-OpenSSHBuild.ps1 to check warnings (success or failure) test_result = test_openssh_build(Configuration="Release", Architecture="x64", LogFile=build_result.log) - + IF build_result.success: // Check for new warnings against baseline new_warnings = compare_warnings_to_baseline(test_result.warnings, baseline_warnings) @@ -321,6 +325,8 @@ FUNCTION determine_fix_strategy(error): - Compare warning count against established baseline - If new warnings detected, report to user with categorization and request approval before proceeding - Do NOT skip `Test-OpenSSHBuild.ps1` even when build succeeds - warning checks are mandatory. +- On the scratch branch, commit build fixes after each batch merge commit. +- On the real branch, apply the same build fixes as separate commits after the single merge commit. ## Testing Automation Framework @@ -423,17 +429,17 @@ FUNCTION assess_conflict_complexity(conflicts): When `assess_conflict_complexity()` returns `HIGH_COMPLEXITY`, invoke the `Get-ConflictContext` MCP tool **before** attempting to edit the file. It provides three-way context anchored to the actual changed regions, accounting for the fact that our fork's line numbers differ from upstream. ```pseudocode -FUNCTION resolve_conflict(file_path, conflict_content, cherry_pick_commit): +FUNCTION resolve_conflict(file_path, conflict_content, merge_batch_commit): complexity = assess_conflict_complexity([{file: file_path, content: conflict_content}]) IF complexity == "HIGH_COMPLEXITY": // Fetch three-way context before editing // MCP Tool: mcp_openssh-server_Get_ConflictContext - // FilePath=file_path, CommitHash=cherry_pick_commit + // FilePath=file_path, CommitHash=merge_batch_commit // // If the default MaxTotalLines=150 is insufficient (e.g., many hunks or // a large function), re-invoke with a higher value such as MaxTotalLines=300. - context = get_conflict_context(file_path, cherry_pick_commit) + context = get_conflict_context(file_path, merge_batch_commit) FOR EACH hunk IN context.Hunks: // Use all three excerpts to understand: @@ -445,9 +451,17 @@ FUNCTION resolve_conflict(file_path, conflict_content, cherry_pick_commit): // Check Message for budget warnings and increase MaxTotalLines if needed IF context.Message contains "minimum floor": - re_invoke_with_larger_budget(file_path, cherry_pick_commit) + re_invoke_with_larger_budget(file_path, merge_batch_commit) + + resolved = apply_resolution_strategy(file_path, conflict_content) + + // Record the resolution for replay on the real branch + // MCP Tool: mcp_openssh-server_Save_MergeResolution + // FilePath=file_path, Strategy=, Rationale=, + // BatchNumber=, UpstreamCommits= + save_merge_resolution(file_path, strategy, rationale, batch_number) - RETURN apply_resolution_strategy(file_path, conflict_content) + RETURN resolved ``` **Key behaviours of the tool:** diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 4f64bf99b092..43b6eb1606d9 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -16,16 +16,20 @@ Ensure the following tools are installed and configured before proceeding: - Latest Windows 10/11 SDK ## Process Overview -The merge process uses a **chunked approach** to reduce complexity and improve success rates. Instead of merging entire upstream versions at once, commits are processed in small batches ending with commits that have CI runs. Each batch is built and optionally validated before proceeding to the next. +The merge process uses a **two-phase approach** to preserve upstream commit history while keeping conflict resolution manageable: + +1. **Scratch branch** — Incremental `git merge` at batch boundaries. Build and test after each batch. Every conflict resolution is recorded via `git rerere` and a resolution log. +2. **Real branch** — A single `git merge` of the final upstream target. Recorded resolutions are replayed automatically. This produces one merge commit with all upstream SHAs intact. The process consists of several interconnected phases: 1. **[Setup Phase](#setup-phase)** - Repository configuration and preparation 2. **[Research Phase](#research-phase)** - Understanding changes and conflicts -3. **[Merge Phase](#merge-phase)** - Performing chunked commit merging -4. **[Build Phase](#build-phase)** - Resolving compilation issues -5. **[Testing Phase](#testing-phase)** - Validating functionality -6. **[Submission Phase](#submission-phase)** - Creating the Pull Request +3. **[Scratch Branch Phase](#scratch-branch-phase)** - Incremental merging with resolution recording +4. **[Real Branch Phase](#real-branch-phase)** - Single merge with resolution replay +5. **[Build Phase](#build-phase)** - Resolving compilation issues +6. **[Testing Phase](#testing-phase)** - Validating functionality +7. **[Submission Phase](#submission-phase)** - Creating the Pull Request ## Setup Phase @@ -68,9 +72,24 @@ The process consists of several interconnected phases: ```pwsh # MCP Tool: mcp_openssh-server_Invoke_Git # Operation="Config", Key="core.editor", Value="true" + + # Enable rerere for automatic resolution recording + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Config", Key="rerere.enabled", Value="true" + ``` + +3. **Create branches:** + ```pwsh + # Create the real merge branch (receives the final single merge) + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CreateBranch", Branch="merge-v-" + + # Create the scratch branch from the same point (for incremental merges) + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="CreateBranch", Branch="scratch-merge-v-" ``` -### Perform Merge with Grouped Commits +### Scratch Branch Phase 4. **Identify merge range and group commits:** Use the Get-CommitGroups MCP tool with `-FirstChunkOnly -GroupByCIPresence` @@ -103,61 +122,47 @@ The process consists of several interconnected phases: } ``` - Display batch details for verification, then proceed with cherry-picking. + Display batch details for verification, then proceed with merging. **After completing steps below, get next batch**: - Call tool with `StartCommit=`, `EndCommit=`, `FirstChunkOnly=true`, `GroupByCIPresence=true` - Continue this process until the target end commit is reached (or HEAD if no end commit specified) -5. **Execute chunked merge (batch cherry-pick all commits in chunk):** +5. **Execute batch merge on scratch branch:** - **Important:** Cherry-pick **all commits in the batch** before building. This is more efficient than building after each individual commit. If the build fails, you can identify the problematic commit(s) through git bisect or by examining the build errors. + Merge the batch's end commit to bring in all commits in the range at once: ```pwsh - # For each chunk (start..end), cherry-pick all commits in the batch - # Example for a single chunk: - $chunkStart = $result.StartCommitFull - $chunkEnd = $result.EndCommitFull - - # Get commits in oldest-first order using Invoke-Git MCP tool: + # Merge all commits up to the batch endpoint # MCP Tool: mcp_openssh-server_Invoke_Git - # Operation="Log", Range="$chunkStart^..$chunkEnd", ShasOnly=true - # result.Commits contains [{Hash, Message}] in oldest-first order - - foreach ($commit in $result.Commits) { - # Cherry-pick each commit using Invoke-Git MCP tool: - # MCP Tool: mcp_openssh-server_Invoke_Git - # Operation="CherryPick", CommitHash=$commit.Hash - - # If conflicts occur, resolve them before continuing to next commit - # (see step 7 below for conflict resolution) - } - - # After all commits in batch are cherry-picked, proceed to build (step 9) + # Operation="Merge", CommitHash=$result.EndCommitFull ``` -7. **Resolve merge conflicts (per commit):** + This uses `--no-ff` to always create a merge commit checkpoint. + +7. **Resolve merge conflicts and record resolutions:** **📖 Detailed Instructions** ([Merge Details](./merge-details.instructions.md)): - - Resolve conflicts for the current commit only - - Use three-way comparison tools + - Resolve conflicts for all conflicted files + - **Record each resolution** using the Save-MergeResolution MCP tool: + ```pwsh + # MCP Tool: mcp_openssh-server_Save_MergeResolution + # FilePath="", Strategy="", Rationale="", + # BatchNumber=, UpstreamCommits="" + ``` + - `git rerere` also automatically records resolutions - Follow established Windows compatibility patterns - Reference previous merge PRs for similar conflicts -8. **Continue cherry-picking after resolution:** +8. **Complete the merge after resolution:** ```pwsh - # After resolving conflicts for current commit - # Stage all resolved files using Invoke-Git MCP tool: # MCP Tool: mcp_openssh-server_Invoke_Git # Operation="Add", Path="." - # Continue cherry-pick using Invoke-Git MCP tool: + # Continue merge using Invoke-Git MCP tool: # MCP Tool: mcp_openssh-server_Invoke_Git - # Operation="CherryPickContinue" - - # Then continue with remaining commits in batch - # Repeat process for next batch if needed + # Operation="MergeContinue" ``` 9. **Build after completing the batch:** @@ -170,11 +175,11 @@ The process consists of several interconnected phases: - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHBuild` - **Parameters**: `Configuration="Release"`, `Architecture="x64"` - **DO NOT** try to read log files directly with `Get-Content` or locate them manually - + If build failed: - Fix issues based on parsed error output - Rebuild and verify - + If build succeeded: - Compare warnings against baseline established in Phase 1 - If new warnings detected: @@ -183,7 +188,7 @@ The process consists of several interconnected phases: - Request user decision: fix warnings or proceed - Wait for user approval before continuing - If user approves proceeding, update baseline to include new warnings - + **CRITICAL: Before committing, restore paths.targets**: ```pwsh # MCP Tool: mcp_openssh-server_Invoke_Git @@ -203,6 +208,38 @@ The process consists of several interconnected phases: - Summarize batch changes, conflicts resolved, build status, validation status - Wait for user approval before proceeding to next batch - Document next steps (starting commit for next batch) + - After all batches complete on the scratch branch, proceed to the Real Branch Phase + +### Real Branch Phase +12. **Switch to the real merge branch:** + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Checkout", Target="merge-v-" + ``` + +13. **Perform a single merge** of the final upstream target: + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Merge", CommitHash="" + ``` + `git rerere` will automatically apply resolutions recorded during the scratch phase. + +14. **Replay remaining resolutions** from the log: + ```pwsh + # MCP Tool: mcp_openssh-server_Replay_MergeResolutions + # (reads from .git/merge-resolution-log.json) + ``` + Resolve any unmatched files manually using the log's strategy and rationale as guidance. + +15. **Complete the merge and apply build fixes:** + ```pwsh + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="MergeContinue" + ``` + Apply build fixes (config.h.vs, .vcxproj changes, etc.) as separate commits after the merge commit. + +16. **Final build and validation** on the real branch. + --- ## Build Phase diff --git a/.github/prompt/merge.prompt.md b/.github/prompt/merge.prompt.md index a75572557e79..0089b1bcbbba 100644 --- a/.github/prompt/merge.prompt.md +++ b/.github/prompt/merge.prompt.md @@ -12,7 +12,7 @@ Provide the following when you invoke this prompt: Operating guidance: - Use and follow merge-upstream.agent.md. Treat it as the primary operating guide. - Rely on the provided for repository overview, setup, build, merge strategy, and testing. Do not re-fetch or re-search them; assume they are already attached in context. -- Perform chunked merges ending at commits with CI runs; build after each batch. Only parse build logs if the build fails. +- Use the two-phase merge workflow: (1) incremental `git merge` on a scratch branch with resolution recording via `git rerere` and Save-MergeResolution, then (2) a single `git merge` on the real branch with resolution replay via `git rerere` and Replay-MergeResolutions. This preserves upstream commit history. - Build using the MCP tools: `mcp_openssh-server_Start_OpenSSHBuild` (Release/x64 by default). If the build fails, analyze with `mcp_openssh-server_Test_OpenSSHBuild`. - Apply Windows compatibility strategies as documented (prefer win32compat layer; guard with `#ifdef WINDOWS` when necessary; update VS projects for build system changes). - Summarize a plan, request approval between batches, and clearly list conflict resolutions and rationale. diff --git a/.github/tools/Get-ConflictContext.ps1 b/.github/tools/Get-ConflictContext.ps1 index e83d6a4c4fd3..46bab7be45b1 100644 --- a/.github/tools/Get-ConflictContext.ps1 +++ b/.github/tools/Get-ConflictContext.ps1 @@ -3,10 +3,10 @@ Retrieves three-way context for a conflicted file to aid complex conflict resolution. .DESCRIPTION - MCP-compatible tool that fetches three versions of a file involved in a cherry-pick - conflict — the upstream file before the commit, the upstream file after the commit, - and our fork's version (via MERGE_HEAD) — and extracts focused, line-numbered excerpts - centered on each changed hunk. + MCP-compatible tool that fetches three versions of a file involved in a merge or + cherry-pick conflict — the upstream file before the commit, the upstream file after + the commit, and our fork's version (via HEAD) — and extracts focused, line-numbered + excerpts centered on each changed hunk. For each hunk in the upstream diff, the tool locates the corresponding region in our fork using two-tier content matching: @@ -22,7 +22,8 @@ Path to the conflicted file, relative to the repository root. .PARAMETER CommitHash - The upstream commit SHA being cherry-picked that caused the conflict. + The upstream commit SHA that caused the conflict. During the scratch-branch merge + workflow, this is typically the batch endpoint commit passed to git merge. .PARAMETER ContextLines Number of lines of context above and below each hunk match to include in excerpts. @@ -344,11 +345,11 @@ $commitMsgResult = Invoke-GitCommand -Arguments @('log', '-1', '--pretty=format: $commitMessage = if ($commitMsgResult.Success) { $commitMsgResult.Output.Trim() } else { '' } # 5. Fetch the three file versions -# MERGE_HEAD is populated by git during an in-progress cherry-pick conflict -# and points to the commit being cherry-picked (our fork's working state before merge) +# HEAD is our fork's version of the file during both merge and cherry-pick conflicts. +# CommitHash^ is the upstream state before the commit; CommitHash is after. $upstreamBefore = Get-FileAtRef -Ref "${CommitHash}^" -File $FilePath $upstreamAfter = Get-FileAtRef -Ref "${CommitHash}" -File $FilePath -$ourFork = Get-FileAtRef -Ref 'MERGE_HEAD' -File $FilePath +$ourFork = Get-FileAtRef -Ref 'HEAD' -File $FilePath # 6. Parse hunks from the diff $hunks = Parse-DiffHunks -DiffText $upstreamDiff diff --git a/.github/tools/Invoke-Git.ps1 b/.github/tools/Invoke-Git.ps1 index f091e32b016c..3a30a1f53ade 100644 --- a/.github/tools/Invoke-Git.ps1 +++ b/.github/tools/Invoke-Git.ps1 @@ -15,6 +15,7 @@ .PARAMETER Operation The git operation to perform. One of: CherryPick, CherryPickContinue, CherryPickAbort, + Merge, MergeContinue, MergeAbort, Add, Checkout, CreateBranch, Commit, Push, Fetch, Config, Reset, Clean, @@ -22,7 +23,7 @@ .PARAMETER CommitHash A commit SHA or any git ref (branch name, tag, etc.). - Used by: CherryPick, Show. + Used by: CherryPick, Merge, Show. .PARAMETER Range A git range expression (e.g. "abc123^..def456" or "HEAD..upstream/V_10_0_P2"). @@ -78,6 +79,7 @@ Message [string] Human-readable summary Plus operation-specific fields: CherryPick (on failure): ConflictedFiles [string[]] + Merge (on failure): ConflictedFiles [string[]] Log / ShasOnly: Commits [{Hash, Message}] Status: ConflictedFiles [string[]], ModifiedFiles [string[]] Commit (on success): CommitHash [string] @@ -152,6 +154,26 @@ # MCP Tool: mcp_openssh-server_Invoke_Git # Operation="Reset", Target="HEAD", Mode="hard" +.EXAMPLE + # Merge an upstream batch endpoint into the current branch + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Merge", CommitHash="6fb728df50c1afd338cb0223a84ce24579577eff" + +.EXAMPLE + # Continue a merge after resolving conflicts + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="MergeContinue" + +.EXAMPLE + # Abort an in-progress merge + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="MergeAbort" + +.EXAMPLE + # Enable rerere for merge resolution recording + # MCP Tool: mcp_openssh-server_Invoke_Git + # Operation="Config", Key="rerere.enabled", Value="true" + .EXAMPLE # Remove untracked files (recovery) # MCP Tool: mcp_openssh-server_Invoke_Git @@ -162,6 +184,7 @@ param( [Parameter(Mandatory)] [ValidateSet( 'CherryPick', 'CherryPickContinue', 'CherryPickAbort', + 'Merge', 'MergeContinue', 'MergeAbort', 'Add', 'Checkout', 'CreateBranch', 'Commit', 'Push', 'Fetch', 'Config', 'Reset', 'Clean', @@ -169,7 +192,7 @@ param( )] [string]$Operation, - # CherryPick, Show — accepts any git ref: commit SHA, branch name, tag, etc. + # CherryPick, Merge, Show — accepts any git ref: commit SHA, branch name, tag, etc. [string]$CommitHash = '', # Log (plain or ShasOnly), Diff — git range expression e.g. "abc123^..def456" @@ -290,6 +313,26 @@ $result = switch ($Operation) { Invoke-GitCommand -Arguments @('cherry-pick', '--abort') } + 'Merge' { + if (-not $CommitHash) { throw 'CommitHash is required for Merge (target ref to merge)' } + $r = Invoke-GitCommand -Arguments @('merge', '--no-ff', $CommitHash) + if (-not $r.Success) { + $statusResult = Invoke-GitCommand -Arguments @('status', '--porcelain') + $r['ConflictedFiles'] = ($statusResult.Output -split "`n") | + Where-Object { $_ -match '^(UU|AA|DD|AU|UA|DU|UD)\s' } | + ForEach-Object { $_.Substring(3).Trim() } + } + $r + } + + 'MergeContinue' { + Invoke-GitCommand -Arguments @('merge', '--continue') + } + + 'MergeAbort' { + Invoke-GitCommand -Arguments @('merge', '--abort') + } + 'Add' { Invoke-GitCommand -Arguments @('add', $Path) } diff --git a/.github/tools/Replay-MergeResolutions.ps1 b/.github/tools/Replay-MergeResolutions.ps1 new file mode 100644 index 000000000000..7006c44de9a9 --- /dev/null +++ b/.github/tools/Replay-MergeResolutions.ps1 @@ -0,0 +1,201 @@ +<# +.SYNOPSIS + Replays saved conflict resolutions from the merge resolution log onto + currently conflicted files. + +.DESCRIPTION + MCP-compatible tool for the real-branch phase of the scratch-branch merge + workflow. Reads .git/merge-resolution-log.json (populated during the scratch + phase by Save-MergeResolution.ps1) and attempts to apply saved resolutions + to files that are currently in a conflicted state (UU/AA/etc. in git status). + + For each conflicted file that has a matching entry in the log, the tool writes + the saved resolved content and stages the file with git add. + + Files not found in the log are reported as unmatched so the agent can resolve + them manually. + +.PARAMETER DryRun + When specified, reports what would be applied without modifying any files. + +.OUTPUTS + Hashtable with: + Success [bool] Whether the operation completed without errors + Message [string] Human-readable summary + ResolvedFiles [string[]] Files successfully resolved from the log + UnmatchedFiles [string[]] Conflicted files with no log entry + FailedFiles [string[]] Files where replay was attempted but failed + LogPath [string] Absolute path to the resolution log + +.EXAMPLE + # Replay all saved resolutions onto current merge conflicts + # MCP Tool: mcp_openssh-server_Replay_MergeResolutions + +.EXAMPLE + # Preview what would be replayed without modifying files + # MCP Tool: mcp_openssh-server_Replay_MergeResolutions + # DryRun=true +#> + +param( + [switch]$DryRun +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# ── Git process helper (same pattern as Invoke-Git.ps1) ────────────────────── + +function Invoke-GitCommand { + param([string[]]$Arguments) + + $processInfo = [System.Diagnostics.ProcessStartInfo]::new() + $processInfo.FileName = 'git' + $processInfo.Arguments = ($Arguments | ForEach-Object { + if ($_ -match '\s') { "`"$_`"" } else { $_ } + }) -join ' ' + $processInfo.UseShellExecute = $false + $processInfo.CreateNoWindow = $true + $processInfo.RedirectStandardInput = $true + $processInfo.RedirectStandardOutput = $true + $processInfo.RedirectStandardError = $true + + $process = [System.Diagnostics.Process]::new() + $process.StartInfo = $processInfo + $process.Start() | Out-Null + $process.StandardInput.Close() + + $stdoutTask = $process.StandardOutput.ReadToEndAsync() + $stderrTask = $process.StandardError.ReadToEndAsync() + + $completed = $process.WaitForExit(30000) + + if (-not $completed) { + $process.Kill() + return @{ + ExitCode = -1 + Success = $false + Output = '' + Error = "git $($Arguments -join ' ') timed out after 30 seconds" + } + } + + return @{ + ExitCode = $process.ExitCode + Success = ($process.ExitCode -eq 0) + Output = $stdoutTask.GetAwaiter().GetResult().TrimEnd() + Error = $stderrTask.GetAwaiter().GetResult().TrimEnd() + } +} + +# ── Locate and read the resolution log ─────────────────────────────────────── + +$gitDir = Join-Path (Get-Location) '.git' +$logPath = Join-Path $gitDir 'merge-resolution-log.json' + +if (-not (Test-Path $logPath)) { + return @{ + Success = $false + Message = 'No resolution log found at .git/merge-resolution-log.json. Run the scratch-branch phase first.' + ResolvedFiles = @() + UnmatchedFiles = @() + FailedFiles = @() + LogPath = $logPath + } +} + +$log = Get-Content -Raw $logPath | ConvertFrom-Json + +if (-not $log.resolutions -or $log.resolutions.Count -eq 0) { + return @{ + Success = $true + Message = 'Resolution log is empty — no saved resolutions to replay.' + ResolvedFiles = @() + UnmatchedFiles = @() + FailedFiles = @() + LogPath = $logPath + } +} + +# ── Build a lookup of saved resolutions keyed by file path ─────────────────── +# If multiple entries exist for the same file (across batches), use the latest one. + +$resolutionMap = @{} +foreach ($entry in $log.resolutions) { + $resolutionMap[$entry.file] = $entry +} + +# ── Get currently conflicted files from git status ─────────────────────────── + +$statusResult = Invoke-GitCommand -Arguments @('status', '--porcelain') +$conflictedFiles = @() +if ($statusResult.Output) { + $conflictedFiles = ($statusResult.Output -split "`n") | + Where-Object { $_ -match '^(UU|AA|DD|AU|UA|DU|UD)\s' } | + ForEach-Object { $_.Substring(3).Trim() } +} + +if ($conflictedFiles.Count -eq 0) { + return @{ + Success = $true + Message = 'No conflicted files found — nothing to replay.' + ResolvedFiles = @() + UnmatchedFiles = @() + FailedFiles = @() + LogPath = $logPath + } +} + +# ── Replay resolutions ────────────────────────────────────────────────────── + +$resolvedFiles = [System.Collections.Generic.List[string]]::new() +$unmatchedFiles = [System.Collections.Generic.List[string]]::new() +$failedFiles = [System.Collections.Generic.List[string]]::new() + +foreach ($file in $conflictedFiles) { + if (-not $resolutionMap.ContainsKey($file)) { + $unmatchedFiles.Add($file) + continue + } + + $entry = $resolutionMap[$file] + + if ($DryRun) { + $resolvedFiles.Add($file) + continue + } + + try { + # Decode the saved resolved content and write it to the file + $contentBytes = [System.Convert]::FromBase64String($entry.resolved_content_base64) + $fullFilePath = Join-Path (Get-Location) $file + [System.IO.File]::WriteAllBytes($fullFilePath, $contentBytes) + + # Stage the resolved file + $addResult = Invoke-GitCommand -Arguments @('add', $file) + if (-not $addResult.Success) { + $failedFiles.Add($file) + continue + } + + $resolvedFiles.Add($file) + } catch { + $failedFiles.Add($file) + } +} + +# ── Return result ──────────────────────────────────────────────────────────── + +$dryRunNote = if ($DryRun) { ' (DRY RUN — no files modified)' } else { '' } +$message = "Replay complete${dryRunNote}: $($resolvedFiles.Count) resolved, " + + "$($unmatchedFiles.Count) unmatched, $($failedFiles.Count) failed " + + "(out of $($conflictedFiles.Count) conflicted files)." + +return @{ + Success = ($failedFiles.Count -eq 0) + Message = $message + ResolvedFiles = $resolvedFiles.ToArray() + UnmatchedFiles = $unmatchedFiles.ToArray() + FailedFiles = $failedFiles.ToArray() + LogPath = $logPath +} diff --git a/.github/tools/Save-MergeResolution.ps1 b/.github/tools/Save-MergeResolution.ps1 new file mode 100644 index 000000000000..049d0e739172 --- /dev/null +++ b/.github/tools/Save-MergeResolution.ps1 @@ -0,0 +1,160 @@ +<# +.SYNOPSIS + Records a conflict resolution entry to the merge resolution log. + +.DESCRIPTION + MCP-compatible tool that saves details about how a conflicted file was resolved + during the scratch-branch phase of the merge workflow. The resolution — including + the resolved file content, strategy, and rationale — is appended to a JSON log + at .git/merge-resolution-log.json. + + This log is consumed by Replay-MergeResolutions.ps1 during the real-branch + single-merge phase to automatically re-apply known resolutions. + +.PARAMETER FilePath + Path to the resolved file, relative to the repository root. + +.PARAMETER Strategy + The resolution strategy used. One of: + accept_upstream — Took the upstream change completely + ifdef_windows — Wrapped with #ifdef WINDOWS / #else / #endif + ifndef_windows — Excluded with #ifndef WINDOWS + combine — Combined upstream and Windows changes + manual — Custom resolution not fitting other categories + +.PARAMETER Rationale + Free-text explanation of why this resolution strategy was chosen. + +.PARAMETER BatchNumber + The batch number (from Get-CommitGroups) that this resolution belongs to. + +.PARAMETER UpstreamCommits + Comma-separated list of upstream commit SHAs that touched this file in this batch. + +.PARAMETER MergeTarget + The final upstream ref being merged (e.g. "upstream/V_10_1_P1"). Only needed on + the first invocation to initialise the log header. Ignored if log already exists. + +.OUTPUTS + Hashtable with: + Success [bool] Whether the entry was saved + Message [string] Human-readable summary + LogPath [string] Absolute path to the resolution log file + +.EXAMPLE + # Record a resolution after resolving auth.c + # MCP Tool: mcp_openssh-server_Save_MergeResolution + # FilePath="auth.c", Strategy="ifdef_windows", Rationale="Wrapped PAM code", + # BatchNumber=1, UpstreamCommits="abc1234,def5678" + +.EXAMPLE + # Record the first resolution (initialises log with MergeTarget) + # MCP Tool: mcp_openssh-server_Save_MergeResolution + # FilePath="channels.c", Strategy="accept_upstream", Rationale="Security fix", + # BatchNumber=1, UpstreamCommits="abc1234", MergeTarget="upstream/V_10_1_P1" +#> + +param( + [Parameter(Mandatory)] + [string]$FilePath, + + [Parameter(Mandatory)] + [ValidateSet('accept_upstream', 'ifdef_windows', 'ifndef_windows', 'combine', 'manual')] + [string]$Strategy, + + [Parameter(Mandatory)] + [string]$Rationale, + + [Parameter(Mandatory)] + [int]$BatchNumber, + + [string]$UpstreamCommits = '', + + [string]$MergeTarget = '' +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# ── Locate the log file inside .git ────────────────────────────────────────── + +$gitDir = Join-Path (Get-Location) '.git' +if (-not (Test-Path $gitDir)) { + return @{ + Success = $false + Message = 'Not inside a git repository — .git directory not found.' + LogPath = '' + } +} + +$logPath = Join-Path $gitDir 'merge-resolution-log.json' + +# ── Read or initialise the log ─────────────────────────────────────────────── + +if (Test-Path $logPath) { + $log = Get-Content -Raw $logPath | ConvertFrom-Json +} else { + $log = [PSCustomObject]@{ + merge_target = if ($MergeTarget) { $MergeTarget } else { 'unknown' } + created_at = (Get-Date -Format 'o') + resolutions = @() + } +} + +# ── Read the resolved file and compute hash ────────────────────────────────── + +$fullFilePath = Join-Path (Get-Location) $FilePath +if (-not (Test-Path $fullFilePath)) { + return @{ + Success = $false + Message = "Resolved file not found: ${FilePath}" + LogPath = $logPath + } +} + +$fileBytes = [System.IO.File]::ReadAllBytes($fullFilePath) +$sha256 = [System.Security.Cryptography.SHA256]::Create() +$hashBytes = $sha256.ComputeHash($fileBytes) +$hashString = ($hashBytes | ForEach-Object { $_.ToString('x2') }) -join '' +$contentB64 = [System.Convert]::ToBase64String($fileBytes) + +# ── Parse upstream commits ─────────────────────────────────────────────────── + +$commitList = if ($UpstreamCommits) { + ($UpstreamCommits -split ',') | ForEach-Object { $_.Trim() } | Where-Object { $_ } +} else { + @() +} + +# ── Build the entry ────────────────────────────────────────────────────────── + +$entry = [PSCustomObject]@{ + file = $FilePath + batch_number = $BatchNumber + upstream_commits = $commitList + strategy = $Strategy + rationale = $Rationale + resolved_content_sha256 = $hashString + resolved_content_base64 = $contentB64 + recorded_at = (Get-Date -Format 'o') +} + +# ── Append and save ────────────────────────────────────────────────────────── + +# ConvertFrom-Json returns a fixed-size array; convert to a list so we can add +$existingResolutions = [System.Collections.Generic.List[object]]::new() +if ($log.resolutions) { + foreach ($r in $log.resolutions) { + $existingResolutions.Add($r) + } +} +$existingResolutions.Add($entry) +$log.resolutions = $existingResolutions.ToArray() + +$log | ConvertTo-Json -Depth 10 | Set-Content -Path $logPath -Encoding utf8 + +return @{ + Success = $true + Message = "Resolution recorded for '${FilePath}' (batch ${BatchNumber}, strategy: ${Strategy}). Log has $($log.resolutions.Count) entries." + LogPath = $logPath +} From 232775da6515f4855370907d970a65e6f39bb44a Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Wed, 15 Apr 2026 12:21:42 -0400 Subject: [PATCH 64/68] refine instructions --- .github/agents/merge-upstream.agent.md | 5 +++ .../merge/merge-details.instructions.md | 37 +++++++++++++++++++ .../merge-process-overview.instructions.md | 10 ++++- .github/instructions/testing.instructions.md | 36 +++++++++++++++++- .github/prompt/merge.prompt.md | 3 ++ 5 files changed, 88 insertions(+), 3 deletions(-) diff --git a/.github/agents/merge-upstream.agent.md b/.github/agents/merge-upstream.agent.md index 5bfcdd7deea4..775b9fa3b9c3 100644 --- a/.github/agents/merge-upstream.agent.md +++ b/.github/agents/merge-upstream.agent.md @@ -53,6 +53,11 @@ This agent assists with merging upstream OpenSSH commits into the PowerShell for - `NoCleanup` (boolean, optional): Skip cleanup for debugging (default: false) - **If tool unavailable**: ERROR - This tool is required for the merge workflow + **Validation scenario override:** + - If prompt input declares `Validation scenario=entra-id-debug-localhost`, do not use temporary local-user/password validation. + - Instead, run `sshd -ddd` in one terminal and validate with `ssh localhost` from a second terminal. + - Use this only on machines where the Entra-ID admin account already has key-based auth configured. + 4. **Get-ConflictContext MCP Tool** - Three-way conflict context for high-complexity conflicts - **MCP Tool Name**: `mcp_openssh-server_Get_ConflictContext` - **When to use**: ONLY when `assess_conflict_complexity()` returns `HIGH_COMPLEXITY` diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md index a01f5cd605b4..27db390e5b1b 100644 --- a/.github/instructions/merge/merge-details.instructions.md +++ b/.github/instructions/merge/merge-details.instructions.md @@ -215,6 +215,20 @@ FUNCTION add_source_to_project(project_file, source_file): insert_into_project_file(project_file, new_line) ``` +### Pattern 4: OpenSSH 10.3 Split-sshd State Ordering (Windows) + +When upstream changes split pre-auth work between `sshd-session` and `sshd-auth`, preserve the state/message ordering exactly. + +- `sshd-session` (listener/monitor side) should not perform banner exchange that upstream moved to `sshd-auth`. +- For Windows `FORK_NOT_SUPPORTED` post-auth child (`sshd-session -z`), monitor message order matters: + - Receive identification-exchange state first. + - Then receive authenticated user context. + +If this ordering is wrong, common symptoms are: +- pre-auth failures such as banner parsing or signature mismatches +- post-auth `Invalid user` with empty username +- monitor keystate errors like `incomplete message` + ## Common Conflict Patterns ### File System Operations @@ -222,6 +236,11 @@ FUNCTION add_source_to_project(project_file, source_file): - **Signal handling** → Use Windows event mechanisms - **File permissions** → Adapt to Windows ACL model +### Privsep and Monitor State Transitions (Windows) +- For split `sshd-session` / `sshd-auth` flows, keep sender/receiver message ordering identical across monitor channels. +- Do not add ad-hoc state shuttling unless both sender and receiver are updated in lockstep. +- When debugging, verify the first protocol failure point (banner exchange vs KEX vs post-auth keystate) before changing multiple stages at once. + ### Build System Changes - **Makefile additions** → Update Visual Studio project files (use `\r\n` line endings) - **New dependencies** → Check Windows compatibility @@ -560,8 +579,26 @@ FUNCTION prepare_pull_request(): labels: ["upstream-merge", determine_complexity_label()], assignees: get_default_reviewers() } + +FUNCTION normalize_fork_workflow_triggers(): + workflow_files = list_files(".github/workflows/*.yml") + + FOR EACH wf IN workflow_files: + // Policy for PowerShell Windows fork: dispatch-only upstream workflows + ensure_trigger_enabled(wf, "workflow_dispatch") + disable_trigger(wf, "push") + disable_trigger(wf, "pull_request") + disable_trigger(wf, "schedule") + + RETURN "workflow triggers normalized for fork policy" ``` +### Workflow Trigger Policy (Windows Fork) +- Upstream workflow files merged into this fork should default to manual invocation only. +- Keep `workflow_dispatch` active. +- Disable automatic triggers (`push`, `pull_request`, `schedule`) unless the Windows fork explicitly depends on them. +- During final merge review, verify `.github/workflows/*.yml` trigger blocks are policy-compliant. + ## Commit Message Template ``` diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 43b6eb1606d9..8cfa7b8de472 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -321,12 +321,17 @@ The process consists of several interconnected phases: - Title: `Merge upstream OpenSSH ` - Include comprehensive description of changes and resolutions -16. **Address CI/test failures:** +16. **Normalize upstream workflow triggers for Windows fork:** + - Ensure merged upstream workflow files under `.github/workflows/*.yml` are dispatch-only in this fork. + - Keep `workflow_dispatch` enabled and disable automatic triggers (`push`, `pull_request`, `schedule`) unless explicitly required for this fork. + - Preserve trigger blocks as commented context where practical so future re-syncs are straightforward. + +17. **Address CI/test failures:** - Monitor automated tests - Fix any Windows-specific test failures - Ensure all checks pass -17. **Request review:** +18. **Request review:** - Tag appropriate PowerShell team reviewers - Provide context for complex conflict resolutions @@ -339,6 +344,7 @@ The process consists of several interconnected phases: - [ ] Solution builds successfully on Windows - [ ] Basic SSH connection test passes - [ ] All CI tests pass +- [ ] Upstream workflow triggers normalized to dispatch-only for this fork - [ ] PR approved and ready for merge --- diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index c4f675b3a83c..f4544d8b5877 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -98,6 +98,35 @@ The MCP tool performs comprehensive end-to-end testing including: - Before creating pull requests - When debugging SSH connectivity issues +## Validation Scenario Override: Entra-ID Debug Localhost + +Use this scenario when the prompt explicitly declares `Validation scenario=entra-id-debug-localhost`. + +In this scenario, do not create a temporary local user and random password. Instead, validate using an existing Entra-ID administrator account with key-based auth already configured. + +### Steps +1. Open terminal A in the build output directory and run sshd in foreground debug mode: +```pwsh +cd .\bin\x64\Release +.\sshd.exe -ddd +``` + +2. Open terminal B and attempt local key-based connection: +```pwsh +.\ssh.exe localhost +``` + +3. Confirm validation success by checking both sides: +- Client side: successful login on `ssh localhost` using existing key-based auth +- Server side (terminal A): no fatal errors during authentication/session setup + +### Notes +- This mode is intended for machines that already have admin key-based auth configured. +- Keep `sshd -ddd` running only for validation and stop it after the test. +- Use this scenario instead of `Test-OpenSSHFunctionality` when declared in the prompt. +- Use the rebuilt client and server from the same output directory (`.\bin\x64\Release`) to avoid version-mismatch handshake artifacts. +- Do not run extra port probes (for example `Test-NetConnection localhost -Port 22`) between starting `sshd -ddd` and the first `ssh` attempt; probes can consume the one foreground debug session and produce misleading connection-reset/refused behavior. + ## Manual Testing Procedures (For Troubleshooting Only) If the automated MCP tool fails and you need to troubleshoot specific issues manually, follow these procedures: @@ -228,7 +257,7 @@ Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*SSH*"} **Testing is successful when:** - [ ] All expected executables are present after build (verified by Test-OpenSSHBuild MCP tool) - [ ] SSH service installs and starts without errors -- [ ] SSH connection with password authentication succeeds +- [ ] SSH validation succeeds via either password authentication (standard) or `ssh localhost` key-based auth (entra-id-debug-localhost) - [ ] Test command executes successfully via SSH connection - [ ] All resources cleaned up properly after testing @@ -248,6 +277,11 @@ Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*SSH*"} - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - **Parameters**: (use defaults) + If the prompt declares `Validation scenario=entra-id-debug-localhost`, use the Entra-ID debug localhost flow instead: + - Run `.\sshd.exe -ddd` in one terminal from `.\bin\x64\Release` + - Run `.\ssh.exe localhost` in another terminal from `.\bin\x64\Release` + - Report outcome from both client connection behavior and server debug logs + 2. **If test passes**, the merge is validated for basic SSH functionality 3. **If test fails**, use manual procedures and debug mode to diagnose issues diff --git a/.github/prompt/merge.prompt.md b/.github/prompt/merge.prompt.md index 0089b1bcbbba..d5bd78140b6d 100644 --- a/.github/prompt/merge.prompt.md +++ b/.github/prompt/merge.prompt.md @@ -8,12 +8,14 @@ Provide the following when you invoke this prompt: - Upstream remote — optional (default: `upstream`) - Windows fork remote — optional (default: `upstream-pwsh`) - Target branch — optional (default: current branch) +- Validation scenario — optional (default: `standard`); set to `entra-id-debug-localhost` when the machine uses an Entra-ID admin account with existing key-based auth Operating guidance: - Use and follow merge-upstream.agent.md. Treat it as the primary operating guide. - Rely on the provided for repository overview, setup, build, merge strategy, and testing. Do not re-fetch or re-search them; assume they are already attached in context. - Use the two-phase merge workflow: (1) incremental `git merge` on a scratch branch with resolution recording via `git rerere` and Save-MergeResolution, then (2) a single `git merge` on the real branch with resolution replay via `git rerere` and Replay-MergeResolutions. This preserves upstream commit history. - Build using the MCP tools: `mcp_openssh-server_Start_OpenSSHBuild` (Release/x64 by default). If the build fails, analyze with `mcp_openssh-server_Test_OpenSSHBuild`. +- For validation, default to `mcp_openssh-server_Test_OpenSSHFunctionality`. If `Validation scenario=entra-id-debug-localhost` is declared, skip temporary local-user/password validation and instead run sshd in debug mode (`sshd -ddd`) and validate from a second terminal using `ssh localhost`. - Apply Windows compatibility strategies as documented (prefer win32compat layer; guard with `#ifdef WINDOWS` when necessary; update VS projects for build system changes). - Summarize a plan, request approval between batches, and clearly list conflict resolutions and rationale. @@ -27,6 +29,7 @@ Quick start examples: - "Merge from tag `upstream/V_9_8_P1` into my current branch." - "Merge from tag `upstream/V_9_8_P1` to commit `a1b2c3d` into my current branch." - "Merge starting at commit `3a1b2c3`, upstream remote `upstream`, target current branch." +- "Merge from tag `upstream/V_10_0_P2` to `upstream/V_10_3_P1`, validation scenario `entra-id-debug-localhost`." If the Start ref is not provided, ask for it before proceeding. If the End ref is not provided, merging will continue to HEAD (most recent upstream commit). From f06d9c80957e8a47b2f65f06daa87ed40ddcca6f Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Thu, 16 Apr 2026 11:33:25 -0400 Subject: [PATCH 65/68] add tool to invoke E2E CI --- .../merge/merge-details.instructions.md | 14 +- .../merge-process-overview.instructions.md | 19 +- .github/instructions/testing.instructions.md | 94 ++++- .github/tools/Invoke-OpenSSHTests.ps1 | 338 ++++++++++++++++++ 4 files changed, 454 insertions(+), 11 deletions(-) create mode 100644 .github/tools/Invoke-OpenSSHTests.ps1 diff --git a/.github/instructions/merge/merge-details.instructions.md b/.github/instructions/merge/merge-details.instructions.md index 27db390e5b1b..495f5ab7b0f3 100644 --- a/.github/instructions/merge/merge-details.instructions.md +++ b/.github/instructions/merge/merge-details.instructions.md @@ -10,14 +10,14 @@ This AI-specific documentation provides comprehensive instructions and algorithm **Key Approach: Two-Phase Merge with Scratch Branch** Instead of cherry-picking commits (which rewrites history), this framework implements a two-phase approach: -1. **Scratch branch** — Incremental `git merge` at batch boundaries (grouped by CI presence). Build and test after each batch. Every conflict resolution is recorded via `git rerere` and the Save-MergeResolution MCP tool. +1. **Scratch branch** — Incremental `git merge` at batch boundaries (grouped by CI presence). Build and run the full CI test suite after each batch. Every conflict resolution is recorded via `git rerere` and the Save-MergeResolution MCP tool. 2. **Real branch** — A single `git merge` of the final upstream target. Recorded resolutions replay automatically via `git rerere` and Replay-MergeResolutions. This produces one merge commit with all upstream SHAs intact. Benefits: - Preserves upstream commit history exactly (original SHAs, authors, timestamps) - Uses incremental merge on scratch branch so conflict markers match the final merge (maximising `rerere` replay) - Builds after each batch on scratch branch (mandatory) for early error detection -- Validates functionality only at successful CI checkpoints +- Runs the full CI test suite after each batch (mandatory), independent of upstream CI status - Requires user approval before proceeding to next batch - Allows for incremental progress and easier rollback - Reduces complexity of conflict resolution @@ -347,6 +347,16 @@ FUNCTION determine_fix_strategy(error): - On the scratch branch, commit build fixes after each batch merge commit. - On the real branch, apply the same build fixes as separate commits after the single merge commit. +### Batch Test Invocation Policy (Scratch Branch) + +- After each batch merge is completed and builds cleanly, run the full CI test suite: + - **MCP Tool Name**: `mcp_openssh-server_Invoke_OpenSSHTests` + - **Parameters**: `Configuration="Release"`, `Architecture="x64"`, `TestSuite="All"` +- This is mandatory for every batch, regardless of whether the upstream endpoint commit had successful CI. +- If any suite fails, re-run only the failing suite while fixing (`TestSuite="Unit"`, `TestSuite="Bash"`, `TestSuite="E2E"`). +- For bash triage, run a single failing test with `BashTestFilePath`. +- Do not proceed to the next batch until the full suite passes (or user explicitly approves an exception). + ## Testing Automation Framework ### Automated Test Execution diff --git a/.github/instructions/merge/merge-process-overview.instructions.md b/.github/instructions/merge/merge-process-overview.instructions.md index 8cfa7b8de472..e2787a0edaa9 100644 --- a/.github/instructions/merge/merge-process-overview.instructions.md +++ b/.github/instructions/merge/merge-process-overview.instructions.md @@ -18,7 +18,7 @@ Ensure the following tools are installed and configured before proceeding: ## Process Overview The merge process uses a **two-phase approach** to preserve upstream commit history while keeping conflict resolution manageable: -1. **Scratch branch** — Incremental `git merge` at batch boundaries. Build and test after each batch. Every conflict resolution is recorded via `git rerere` and a resolution log. +1. **Scratch branch** — Incremental `git merge` at batch boundaries. Build and run the full CI test suite after each batch. Every conflict resolution is recorded via `git rerere` and a resolution log. 2. **Real branch** — A single `git merge` of the final upstream target. Recorded resolutions are replayed automatically. This produces one merge commit with all upstream SHAs intact. The process consists of several interconnected phases: @@ -196,16 +196,19 @@ The process consists of several interconnected phases: ``` Commit any build fixes separately with descriptive messages (only actual code changes) -10. **Validate if batch ended with successful CI:** - Check the end commit's CI status from Get-CommitGroups output. - If CI was successful, run validation: - - **MCP Tool Name**: `mcp_openssh-server_Test_OpenSSHFunctionality` - - **Parameters**: (use defaults for Release/x64) +10. **Run full CI validation after every batch (mandatory):** + Run the full OpenSSH CI suite regardless of upstream CI status for the batch endpoint. + - **MCP Tool Name**: `mcp_openssh-server_Invoke_OpenSSHTests` + - **Parameters**: `Configuration="Release"`, `Architecture="x64"`, `TestSuite="All"` - If no CI or failed CI, skip validation (build success is sufficient). + If any suite fails: + - Capture failing suite details from tool output + - Re-run only the failing suite to iterate faster (`TestSuite="Unit"`, `TestSuite="Bash"`, or `TestSuite="E2E"`) + - For a single failing bash case, use `TestSuite="Bash"` and `BashTestFilePath=""` + - Fix issues and re-run full suite before proceeding to the next batch 11. **Provide summary and get approval:** - - Summarize batch changes, conflicts resolved, build status, validation status + - Summarize batch changes, conflicts resolved, build status, and full CI suite status (Unit/Bash/E2E) - Wait for user approval before proceeding to next batch - Document next steps (starting commit for next batch) - After all batches complete on the scratch branch, proceed to the Real Branch Phase diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index f4544d8b5877..5ad56413cc40 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -98,6 +98,92 @@ The MCP tool performs comprehensive end-to-end testing including: - Before creating pull requests - When debugging SSH connectivity issues +## Full CI Test Suite + +For thorough validation (e.g., before submitting a PR or after a significant merge), run the complete CI test suite: unit tests, bash regression tests, and Pester E2E tests. + +Reference: https://github.com/PowerShell/Win32-OpenSSH/wiki/Run-OpenSSH-Pester-Tests + +### Using the Invoke-OpenSSHTests MCP Tool (Recommended) + +Use the Invoke-OpenSSHTests MCP tool: +- **MCP Tool Name**: `mcp_openssh-server_Invoke_OpenSSHTests` +- **Parameters**: + - `Configuration` (optional): "Debug" or "Release" (default: "Release") + - `Architecture` (optional): "x64", "x86", "ARM", "ARM64" (default: "x64") + - `TestSuite` (optional): "All", "Unit", "Bash", "E2E" — one or more values (default: "All") + - `BashTestFilePath` (optional): Absolute path to a single `.sh` test file for targeted bash testing (e.g., `C:\repos\openssh-portable\regress\banner.sh`) + - `BashShellPath` (optional): Path to `sh.exe` — auto-detected from common Cygwin locations if omitted + - `NoCleanup` (optional): Skip `Clear-OpenSSHTestEnvironment` after the run (default: false) + - `SkipSetup` (optional): Skip `Set-OpenSSHTestEnvironment` when environment is already configured (default: false) + +**The tool returns a structured result with:** +- `Success`: Overall pass/fail +- `UnitTestsPassed`, `BashTestsPassed`, `E2ETestsPassed`: Per-suite results (`$true`/`$false`/`$null` if not run) +- `UnitTestOutput`, `BashTestOutput`, `E2ETestOutput`: Captured output for each suite +- `Errors`: Array of failure messages +- `Warnings`: Known gotchas and environment notes +- `Message`: Summary + +**Examples:** +- Run full suite: (no parameters needed) +- Run only E2E tests: `TestSuite="E2E"` +- Run a single bash test: `TestSuite="Bash"`, `BashTestFilePath="C:\repos\openssh-portable\regress\banner.sh"` + +### Manually Running the Full CI Suite + +Binaries are expected at `C:\repos\openssh-portable\bin\{Architecture}\{Configuration}`. + +```pwsh +# 1. Import the test helper module +Import-Module C:\repos\openssh-portable\contrib\win32\openssh\OpenSSHTestHelper.psm1 -Force + +# 2. Configure the test environment (installs test accounts, sshd test service, etc.) +# This modifies known_hosts and ssh_config; run Clear-OpenSSHTestEnvironment to undo. +Set-OpenSSHTestEnvironment -OpenSSHBinPath "C:\repos\openssh-portable\bin\x64\Release" -Confirm:$false + +# 3. Run unit tests (unittest-*.exe binaries in the bin folder) +Invoke-OpenSSHUnitTest + +# 4. Run bash regression tests (requires Cygwin sh.exe) +Invoke-OpenSSHBashTests + +# 5. Run Pester E2E tests +Invoke-OpenSSHE2ETest + +# 6. Clean up test accounts, service, and ssh config changes +Clear-OpenSSHTestEnvironment +``` + +### Running a Single Bash Test + +Use `bash_tests_iterator.ps1` to run one bash test file in isolation: + +```pwsh +.\contrib\win32\openssh\bash_tests_iterator.ps1 ` + -OpenSSHBinPath "C:\repos\openssh-portable\bin\x64\Release" ` + -BashTestsPath "C:\repos\openssh-portable\regress" ` + -ShellPath "C:\cygwin64\bin\sh.exe" ` + -TestFilePath "C:\repos\openssh-portable\regress\banner.sh" +``` + +### Known CI Test Gotchas + +**`cfginclude.sh` — wrong PowerShell executable:** +The test calls `powershell.exe` directly. When running under `pwsh.exe`, the test will fail unless the file is edited to replace `powershell.exe` with `pwsh.exe`. +See: https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850 + +**WSMan / Port Forwarding tests — disabled on some VMs:** +The WSMan and port-forwarding Pester tests may fail on VMs where these Windows features are disabled by default. Options: +- Enable the features: turn on "Windows Remote Management" and ensure port-forward firewall rules are allowed. +- Skip the affected test files (e.g., `PortForwarding.Tests.ps1`) when running `Invoke-OpenSSHE2ETest` for routine validation. + +**Pester version requirement:** +The E2E tests require **Pester version < 5**. The helper module will attempt to install Pester 3.4.6 via chocolatey if a compatible version is not found. + +**Cygwin required for bash tests:** +`Invoke-OpenSSHBashTests` auto-detects `sh.exe` at `%SystemDrive%\cygwin64\bin\sh.exe`, `%SystemDrive%\cygwin\bin\sh.exe`, or `%SystemDrive%\tools\cygwin\bin\sh.exe`. If none is found it installs Cygwin via chocolatey. Provide `-BashShellPath` to the MCP tool to override. + ## Validation Scenario Override: Entra-ID Debug Localhost Use this scenario when the prompt explicitly declares `Validation scenario=entra-id-debug-localhost`. @@ -286,7 +372,13 @@ Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*SSH*"} 3. **If test fails**, use manual procedures and debug mode to diagnose issues -4. **Document results** in commit message or merge documentation +4. **For full CI validation** (e.g., before creating a PR), run the complete test suite: + - **MCP Tool Name**: `mcp_openssh-server_Invoke_OpenSSHTests` + - **Parameters**: (use defaults to run all suites) + - If a specific suite fails, re-run it in isolation using `TestSuite="Unit"`, `TestSuite="Bash"`, or `TestSuite="E2E"` + - For a single failing bash test: `TestSuite="Bash"`, `BashTestFilePath=""` + +5. **Document results** in commit message or merge documentation ## Manual Test Environment Cleanup diff --git a/.github/tools/Invoke-OpenSSHTests.ps1 b/.github/tools/Invoke-OpenSSHTests.ps1 new file mode 100644 index 000000000000..bcb3be929b30 --- /dev/null +++ b/.github/tools/Invoke-OpenSSHTests.ps1 @@ -0,0 +1,338 @@ +<# +.SYNOPSIS + Runs the OpenSSH full CI test suite (unit tests, bash tests, and E2E/Pester tests). + +.DESCRIPTION + This script automates the full OpenSSH CI test workflow on Windows using the + OpenSSHTestHelper module. It supports running all test suites together or + individual suites, with optional single-test targeting for bash tests. + + Test suites: + - Unit: Runs unittest-*.exe binaries found under the binary path + - Bash: Runs upstream bash regression tests via Cygwin sh.exe + - E2E: Runs Windows Pester-based end-to-end tests (requires Pester < 5) + + The workflow is: + 1. Import OpenSSHTestHelper module + 2. Set-OpenSSHTestEnvironment (installs test accounts, sshd test service, etc.) + 3. Run selected test suites + 4. Clear-OpenSSHTestEnvironment (unless -NoCleanup) + + Reference: https://github.com/PowerShell/Win32-OpenSSH/wiki/Run-OpenSSH-Pester-Tests + +.PARAMETER Configuration + Build configuration. Valid values: 'Debug', 'Release'. Default: 'Release'. + +.PARAMETER Architecture + Target architecture. Valid values: 'x64', 'x86', 'ARM', 'ARM64'. Default: 'x64'. + +.PARAMETER TestSuite + Which test suites to run. Valid values: 'All', 'Unit', 'Bash', 'E2E'. + Default: 'All'. Multiple values allowed. + +.PARAMETER BashTestFilePath + Run a single bash test file instead of the full bash suite. + Must be an absolute path to a .sh file under the regress folder. + Example: C:\repos\openssh-portable\regress\banner.sh + Only applies when TestSuite includes 'Bash'. + +.PARAMETER BashShellPath + Path to sh.exe (Cygwin or WSL). Auto-detected from common Cygwin locations + if not specified. Example: C:\cygwin64\bin\sh.exe + +.PARAMETER NoCleanup + Skip Clear-OpenSSHTestEnvironment at the end. Useful for debugging failures. + +.PARAMETER SkipSetup + Skip Set-OpenSSHTestEnvironment. Use when environment is already configured. + +.EXAMPLE + .\Invoke-OpenSSHTests.ps1 + Runs all test suites with Release x64 binaries. + +.EXAMPLE + .\Invoke-OpenSSHTests.ps1 -TestSuite Unit + Runs only unit tests. + +.EXAMPLE + .\Invoke-OpenSSHTests.ps1 -TestSuite E2E -Configuration Debug + Runs only Pester E2E tests using Debug binaries. + +.EXAMPLE + .\Invoke-OpenSSHTests.ps1 -TestSuite Bash -BashTestFilePath C:\repos\openssh-portable\regress\banner.sh + Runs a single bash test. + +.NOTES + Requires Administrator privileges. + Pester < 5 required for E2E tests (the helper will install via chocolatey if needed). + Cygwin (sh.exe) required for bash tests. + + Known gotchas: + - cfginclude.sh calls powershell.exe; if running under pwsh.exe, edit the test to use pwsh.exe. + See https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850 + - WSMan and Port Forwarding tests may be disallowed on some VMs by default. + Enable the features or skip the affected tests. +#> + +[CmdletBinding()] +param( + [Parameter()] + [ValidateSet('Debug', 'Release')] + [string]$Configuration = 'Release', + + [Parameter()] + [ValidateSet('x64', 'x86', 'ARM', 'ARM64')] + [string]$Architecture = 'x64', + + [Parameter()] + [ValidateSet('All', 'Unit', 'Bash', 'E2E')] + [string[]]$TestSuite = @('All'), + + [Parameter()] + [string]$BashTestFilePath = '', + + [Parameter()] + [string]$BashShellPath = '', + + [Parameter()] + [switch]$NoCleanup, + + [Parameter()] + [switch]$SkipSetup +) + +# ────────────────────────────────────────────────────────────────────────────── +# Resolve paths +# ────────────────────────────────────────────────────────────────────────────── +$scriptRoot = Split-Path -Parent $PSCommandPath +$repoRoot = Split-Path -Parent (Split-Path -Parent $scriptRoot) +$binPath = Join-Path $repoRoot "bin\$Architecture\$Configuration" +$helperModule = Join-Path $repoRoot "contrib\win32\openssh\OpenSSHTestHelper.psm1" +$bashIterator = Join-Path $repoRoot "contrib\win32\openssh\bash_tests_iterator.ps1" +$regressPath = Join-Path $repoRoot "regress" + +# ────────────────────────────────────────────────────────────────────────────── +# Result object +# ────────────────────────────────────────────────────────────────────────────── +$result = [PSCustomObject]@{ + Success = $false + UnitTestsPassed = $null # $true/$false/$null (not run) + BashTestsPassed = $null + E2ETestsPassed = $null + UnitTestOutput = $null + BashTestOutput = $null + E2ETestOutput = $null + Errors = @() + Warnings = @() + Message = '' +} + +$moduleImported = $false +$setupCompleted = $false + +function Write-StepHeader([string]$msg) { + Write-Host "" + Write-Host "=== $msg ===" -ForegroundColor Cyan +} + +$runUnit = $TestSuite -contains 'All' -or $TestSuite -contains 'Unit' +$runBash = $TestSuite -contains 'All' -or $TestSuite -contains 'Bash' +$runE2E = $TestSuite -contains 'All' -or $TestSuite -contains 'E2E' + +try { + # ── Admin check ────────────────────────────────────────────────────────── + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( + [Security.Principal.WindowsBuiltInRole]"Administrator") + if (-not $isAdmin) { + $result.Errors += "Administrator privileges required." + $result.Message = "FAILED: Must be run as Administrator." + Write-Host "✗ Administrator privileges required." -ForegroundColor Red + return $result + } + + # ── Verify binaries ────────────────────────────────────────────────────── + if (-not (Test-Path (Join-Path $binPath "ssh.exe"))) { + $result.Errors += "Binaries not found at $binPath. Build the project first." + $result.Message = "FAILED: Build artifacts not found at $binPath." + Write-Host "✗ Binaries not found at $binPath" -ForegroundColor Red + return $result + } + Write-Host "✓ Binaries found at $binPath" -ForegroundColor Green + + # ── Import helper module ───────────────────────────────────────────────── + Write-StepHeader "Importing OpenSSHTestHelper" + if (-not (Test-Path $helperModule)) { + $result.Errors += "OpenSSHTestHelper.psm1 not found at $helperModule" + $result.Message = "FAILED: OpenSSHTestHelper module not found." + Write-Host "✗ Module not found: $helperModule" -ForegroundColor Red + return $result + } + Import-Module $helperModule -Force + $moduleImported = $true + Write-Host "✓ Module imported" -ForegroundColor Green + + # ── Set-OpenSSHTestEnvironment ─────────────────────────────────────────── + if (-not $SkipSetup) { + Write-StepHeader "Setting Up Test Environment" + Write-Host " OpenSSHBinPath: $binPath" + Set-OpenSSHTestEnvironment -OpenSSHBinPath $binPath -Confirm:$false + $setupCompleted = $true + Write-Host "✓ Test environment configured" -ForegroundColor Green + } else { + Write-Host "⚠ Skipping Set-OpenSSHTestEnvironment (-SkipSetup specified)" -ForegroundColor Yellow + $result.Warnings += "Setup skipped — environment must already be configured." + } + + # ── Unit Tests ─────────────────────────────────────────────────────────── + if ($runUnit) { + Write-StepHeader "Running Unit Tests" + try { + # Force unit test discovery to the selected build output path. + $unitOutput = Invoke-OpenSSHUnitTest -UnitTestDirectory $binPath *>&1 | Tee-Object -Variable unitCapture + $unitText = $unitCapture | Out-String + $result.UnitTestOutput = $unitText + + # Detect failure: unit test runner writes "failed" to output or exits non-zero + if ($unitText -match 'failed') { + $result.UnitTestsPassed = $false + $result.Errors += "Unit tests reported failures. See UnitTestOutput for details." + Write-Host "✗ Unit tests: FAILED" -ForegroundColor Red + } else { + $result.UnitTestsPassed = $true + Write-Host "✓ Unit tests: PASSED" -ForegroundColor Green + } + } catch { + $result.UnitTestsPassed = $false + $result.Errors += "Unit test exception: $_" + Write-Host "✗ Unit tests threw an exception: $_" -ForegroundColor Red + } + } + + # ── Bash Tests ─────────────────────────────────────────────────────────── + if ($runBash) { + Write-StepHeader "Running Bash Tests" + $result.Warnings += "cfginclude.sh gotcha: calls powershell.exe; if running under pwsh, edit the test to use pwsh.exe (see https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850)." + + try { + if (-not [string]::IsNullOrEmpty($BashTestFilePath)) { + # Single bash test via iterator. + $resolvedShellPath = $BashShellPath + if ([string]::IsNullOrEmpty($resolvedShellPath) -or -not (Test-Path $resolvedShellPath)) { + throw "BashShellPath is required for single-test mode." + } + + Write-Host " Shell: $resolvedShellPath" + Write-Host " Running single test: $BashTestFilePath" + $bashOutput = & $bashIterator ` + -OpenSSHBinPath $binPath ` + -BashTestsPath $regressPath ` + -ShellPath $resolvedShellPath ` + -TestFilePath $BashTestFilePath ` + *>&1 | Tee-Object -Variable bashCapture + } else { + # Full bash suite via helper (helper handles Cygwin install/detection). + Write-Host " Running full bash test suite..." + $bashOutput = Invoke-OpenSSHBashTests *>&1 | Tee-Object -Variable bashCapture + } + + $bashText = $bashCapture | Out-String + $result.BashTestOutput = $bashText + + if ($bashText -match 'FAILED|not ok') { + $result.BashTestsPassed = $false + $result.Errors += "Bash tests reported failures. See BashTestOutput for details." + Write-Host "✗ Bash tests: FAILED" -ForegroundColor Red + } else { + $result.BashTestsPassed = $true + Write-Host "✓ Bash tests: PASSED" -ForegroundColor Green + } + } catch { + $result.BashTestsPassed = $false + $result.Errors += "Bash test exception: $_" + Write-Host "✗ Bash tests threw an exception: $_" -ForegroundColor Red + } + } + + # ── E2E / Pester Tests ─────────────────────────────────────────────────── + if ($runE2E) { + Write-StepHeader "Running E2E Pester Tests" + $result.Warnings += "WSMan and Port Forwarding tests may fail on some VMs where those features are disabled. Enable them in Windows Features or skip those test files." + Write-Host " ⚠ Note: WSMan/Port Forwarding may need to be enabled on this machine." -ForegroundColor Yellow + + try { + $e2eOutput = Invoke-OpenSSHE2ETest *>&1 | Tee-Object -Variable e2eCapture + $e2eText = $e2eCapture | Out-String + $result.E2ETestOutput = $e2eText + + if ($e2eText -match 'Failed\s*:\s*[1-9]|Tests failed') { + $result.E2ETestsPassed = $false + $result.Errors += "E2E tests reported failures. See E2ETestOutput for details." + Write-Host "✗ E2E Pester tests: FAILED" -ForegroundColor Red + } else { + $result.E2ETestsPassed = $true + Write-Host "✓ E2E Pester tests: PASSED" -ForegroundColor Green + } + } catch { + $result.E2ETestsPassed = $false + $result.Errors += "E2E test exception: $_" + Write-Host "✗ E2E tests threw an exception: $_" -ForegroundColor Red + } + } + + # ── Overall result ─────────────────────────────────────────────────────── + $anyFailed = ($result.UnitTestsPassed -eq $false) -or + ($result.BashTestsPassed -eq $false) -or + ($result.E2ETestsPassed -eq $false) + + $result.Success = -not $anyFailed + $result.Message = if ($result.Success) { "All selected test suites passed." } ` + else { "One or more test suites failed. See Errors for details." } + +} catch { + $result.Errors += "Unexpected error: $_" + $result.Message = "FAILED: Unexpected error — $_" + Write-Host "✗ Unexpected error: $_" -ForegroundColor Red +} finally { + # ── Cleanup ────────────────────────────────────────────────────────────── + if (-not $NoCleanup -and -not $SkipSetup -and $moduleImported -and $setupCompleted) { + Write-StepHeader "Cleaning Up Test Environment" + try { + Clear-OpenSSHTestEnvironment + Write-Host "✓ Test environment cleaned up" -ForegroundColor Green + } catch { + $result.Warnings += "Cleanup warning: $_" + Write-Host "⚠ Cleanup encountered an issue: $_" -ForegroundColor Yellow + } + } elseif (-not $NoCleanup -and -not $SkipSetup -and (-not $moduleImported -or -not $setupCompleted)) { + $result.Warnings += "Cleanup skipped because setup did not complete." + Write-Host "⚠ Cleanup skipped because setup did not complete." -ForegroundColor Yellow + } elseif ($NoCleanup) { + Write-Host "⚠ Skipping cleanup (-NoCleanup specified). Run Clear-OpenSSHTestEnvironment manually." -ForegroundColor Yellow + } +} + +# ── Summary ─────────────────────────────────────────────────────────────────── +Write-Host "" +Write-Host "=== Test Summary ===" -ForegroundColor Cyan +Write-Host "Overall: $(if ($result.Success) { '✓ PASSED' } else { '✗ FAILED' })" -ForegroundColor $(if ($result.Success) { 'Green' } else { 'Red' }) +if ($null -ne $result.UnitTestsPassed) { + Write-Host "Unit Tests: $(if ($result.UnitTestsPassed) { '✓ PASSED' } else { '✗ FAILED' })" -ForegroundColor $(if ($result.UnitTestsPassed) { 'Green' } else { 'Red' }) +} +if ($null -ne $result.BashTestsPassed) { + Write-Host "Bash Tests: $(if ($result.BashTestsPassed) { '✓ PASSED' } else { '✗ FAILED' })" -ForegroundColor $(if ($result.BashTestsPassed) { 'Green' } else { 'Red' }) +} +if ($null -ne $result.E2ETestsPassed) { + Write-Host "E2E Tests: $(if ($result.E2ETestsPassed) { '✓ PASSED' } else { '✗ FAILED' })" -ForegroundColor $(if ($result.E2ETestsPassed) { 'Green' } else { 'Red' }) +} +if ($result.Warnings.Count -gt 0) { + Write-Host "" + Write-Host "Warnings:" -ForegroundColor Yellow + $result.Warnings | ForEach-Object { Write-Host " ⚠ $_" -ForegroundColor Yellow } +} +if ($result.Errors.Count -gt 0) { + Write-Host "" + Write-Host "Errors:" -ForegroundColor Red + $result.Errors | ForEach-Object { Write-Host " ✗ $_" -ForegroundColor Red } +} + +return $result From ff950b70fc6d3f9d907256b3f7270f55aa4e1996 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Mon, 20 Apr 2026 16:54:44 -0400 Subject: [PATCH 66/68] Fix unittest benchmark compatibility after batch 1 merge - Resolve leftover conflict markers in test_helper.c - Add CLOCK_REALTIME fallback define for Windows unit tests - Add benchmarks() entry point in win32compat unit tests to satisfy benchmark harness linking --- regress/unittests/test_helper/test_helper.c | 7 ++++--- regress/unittests/win32compat/tests.c | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/regress/unittests/test_helper/test_helper.c b/regress/unittests/test_helper/test_helper.c index fcdf0de65bd1..097fd1033065 100644 --- a/regress/unittests/test_helper/test_helper.c +++ b/regress/unittests/test_helper/test_helper.c @@ -60,6 +60,10 @@ #define BENCH_COLUMN_WIDTH 40 #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) + +#ifndef CLOCK_REALTIME +# define CLOCK_REALTIME 0 +#endif #define TEST_CHECK_INT(r, pred) do { \ switch (pred) { \ @@ -450,7 +454,6 @@ assert_string(const char *file, int line, const char *a1, const char *a2, test_die(); } -<<<<<<< HEAD static char * tohex(const void *_s, size_t l) { @@ -468,8 +471,6 @@ tohex(const void *_s, size_t l) return r; } -======= ->>>>>>> f3d465530e75cb6c02e2cde1d15e6c4bb51ebfd9 void assert_mem(const char *file, int line, const char *a1, const char *a2, const void *aa1, const void *aa2, size_t l, enum test_predicate pred) diff --git a/regress/unittests/win32compat/tests.c b/regress/unittests/win32compat/tests.c index ae27730dc757..756d6b8b394c 100644 --- a/regress/unittests/win32compat/tests.c +++ b/regress/unittests/win32compat/tests.c @@ -27,6 +27,12 @@ tests() miscellaneous_tests(); } +void +benchmarks(void) +{ + tests(); +} + char * dup_str(char *inStr) { From 9640f12204e693b88f40a902cf5487da70ffda93 Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Mon, 4 May 2026 13:05:16 -0400 Subject: [PATCH 67/68] Build fixes for batch 9 (V_10_3_P1 merge): misc-agent.c on Windows Upstream commit 80162f9d7 (Move agent listener sockets to ~/.ssh/agent) split agent listener code from session.c and ssh-agent.c into a new file misc-agent.c. Windows fixes: - misc-agent.c: Added include includes.h to pull in win32compat shims. - misc-agent.c: Wrapped socket_is_stale and agent_cleanup_stale in ifndef WINDOWS since they use Unix-only APIs (DT_SOCK, DT_UNKNOWN, dirent.d_type, dirfd, fstatat, unlinkat, st_mtim, timespeccmp, timespecsub, CLOCK_REALTIME) and are only called from upstream ssh-agent.c, which is not built on Windows (Windows uses a separate ssh-agent implementation under contrib/win32/win32compat/ssh-agent). - sshd-auth.vcxproj, sshd-session.vcxproj: Added misc-agent.c so that agent_listener (called from session.c) is linked. Resolves LNK2001 unresolved external symbol agent_listener. agent_listener and its helpers remain available because session.c (built on Windows) calls them; the underlying AF_UNIX socket APIs are supported via win32compat. --- contrib/win32/openssh/sshd-auth.vcxproj | 1 + contrib/win32/openssh/sshd-session.vcxproj | 1 + misc-agent.c | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/contrib/win32/openssh/sshd-auth.vcxproj b/contrib/win32/openssh/sshd-auth.vcxproj index fd3a28e06214..24d5426e513d 100644 --- a/contrib/win32/openssh/sshd-auth.vcxproj +++ b/contrib/win32/openssh/sshd-auth.vcxproj @@ -461,6 +461,7 @@ + diff --git a/contrib/win32/openssh/sshd-session.vcxproj b/contrib/win32/openssh/sshd-session.vcxproj index 45895f0e1bf2..48cd1f6eab02 100644 --- a/contrib/win32/openssh/sshd-session.vcxproj +++ b/contrib/win32/openssh/sshd-session.vcxproj @@ -462,6 +462,7 @@ + diff --git a/misc-agent.c b/misc-agent.c index d065ab0e5b7a..dd12fc273fea 100644 --- a/misc-agent.c +++ b/misc-agent.c @@ -13,7 +13,7 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - +#include "includes.h" #include #include #include @@ -217,6 +217,7 @@ agent_listener(const char *homedir, const char *tag, int *sockp, char **pathp) return 0; } +#ifndef WINDOWS static int socket_is_stale(const char *path) { @@ -326,4 +327,5 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash) free(dirpath); free(prefix); } +#endif /* !WINDOWS */ From df32151d714bf7461721020bf2ad3f1f2efa89aa Mon Sep 17 00:00:00 2001 From: Tess Gauthier Date: Mon, 4 May 2026 14:18:50 -0400 Subject: [PATCH 68/68] Remove ssh-dss.c from libssh.vcxproj after upstream DSA removal Upstream removed ssh-dss.c in batch 15 (commit 93e904a). Build was failing with C1083 because libssh.vcxproj still referenced the deleted source file. Unit test errors (KEY_DSA, sshkey.dsa) will be resolved by upstream commits in batch 16. --- contrib/win32/openssh/libssh.vcxproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/contrib/win32/openssh/libssh.vcxproj b/contrib/win32/openssh/libssh.vcxproj index c6cfe93058f3..dacb2ad311d7 100644 --- a/contrib/win32/openssh/libssh.vcxproj +++ b/contrib/win32/openssh/libssh.vcxproj @@ -422,9 +422,6 @@ - - true - true