public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/openssh] rawhide: Rebase to OpenSSH 10.4p1
@ 2026-07-09  9:41 Dmitry Belyavskiy
  0 siblings, 0 replies; only message in thread
From: Dmitry Belyavskiy @ 2026-07-09  9:41 UTC (permalink / raw)
  To: git-commits

A new commit has been pushed.

Repo   : rpms/openssh
Branch : rawhide
Commit : c8d70d4d0462692a24ba1f5600aa66fdf2021ef4
Author : Dmitry Belyavskiy <dbelyavs@redhat.com>
Date   : 2026-07-09T11:40:42+02:00
Stats  : +9198/-9344 in 74 file(s)
URL    : https://src.fedoraproject.org/rpms/openssh/c/c8d70d4d0462692a24ba1f5600aa66fdf2021ef4?branch=rawhide

Log:
Rebase to OpenSSH 10.4p1

---
diff --git a/.gitignore b/.gitignore
index 2a2a207..36d7adc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,3 +70,5 @@ pam_ssh_agent_auth-0.9.2.tar.bz2
 /openssh-10.2p1.tar.gz.asc
 /openssh-10.3p1.tar.gz
 /openssh-10.3p1.tar.gz.asc
+/openssh-10.4p1.tar.gz
+/openssh-10.4p1.tar.gz.asc

diff --git a/0001-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch b/0001-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
deleted file mode 100644
index bb64edb..0000000
--- a/0001-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
+++ /dev/null
@@ -1,416 +0,0 @@
-From cdb21e545ffde9b237fa9ab7be8e32b78ffc00bb Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 13 Apr 2026 14:06:32 +0200
-Subject: [PATCH 01/54] Add SELinux role and MLS (Multi-Level Security) support
-
-openssh-7.8p1-role-mls
-
-Add support for SELinux role-based access control and Multi-Level
-Security (MLS) by allowing users to specify their role as part of the
-username in the format user/role.
-
-Changes:
-- auth.h: Add role field to Authctxt structure
-- auth2.c: Parse role from username and inform monitor process
-- auth2-gss.c: Include role in GSSAPI MIC authentication
-- auth2-hostbased.c: Include role in hostbased authentication signature
-- auth2-pubkey.c: Include role in pubkey authentication userstyle
-- monitor.c: Add mm_answer_authrole() and handle role in authentication
-  blob validation
-- monitor_wrap.c: Add mm_inform_authrole() for privilege separation
-- misc.c: Update colon() to handle /role syntax in file paths
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- auth.h            |  3 +++
- auth2-gss.c       | 11 ++++++++++-
- auth2-hostbased.c |  9 +++++++++
- auth2-pubkey.c    | 19 +++++++++++++++----
- auth2.c           | 14 ++++++++++++++
- misc.c            | 18 +++++++++++++++++-
- monitor.c         | 37 +++++++++++++++++++++++++++++++++++--
- monitor.h         |  4 ++++
- monitor_wrap.c    | 21 +++++++++++++++++++++
- monitor_wrap.h    |  3 +++
- 10 files changed, 131 insertions(+), 8 deletions(-)
-
-diff --git a/auth.h b/auth.h
-index 634a84aa8..32ee3cbf4 100644
---- a/auth.h
-+++ b/auth.h
-@@ -65,6 +65,9 @@ struct Authctxt {
- 	char		*service;
- 	struct passwd	*pw;		/* set if 'valid' */
- 	char		*style;
-+#ifdef WITH_SELINUX
-+	char		*role;
-+#endif
- 
- 	/* Method lists for multiple authentication */
- 	char		**auth_methods;	/* modified from server config */
-diff --git a/auth2-gss.c b/auth2-gss.c
-index 053548527..b380dc117 100644
---- a/auth2-gss.c
-+++ b/auth2-gss.c
-@@ -284,6 +284,7 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
- 	Authctxt *authctxt = ssh->authctxt;
- 	Gssctxt *gssctxt;
- 	int r, authenticated = 0;
-+	char *micuser;
- 	struct sshbuf *b;
- 	gss_buffer_desc mic, gssbuf;
- 	u_char *p;
-@@ -300,7 +301,13 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
- 		fatal_f("sshbuf_new failed");
- 	mic.value = p;
- 	mic.length = len;
--	ssh_gssapi_buildmic(b, authctxt->user, authctxt->service,
-+#ifdef WITH_SELINUX
-+	if (authctxt->role && authctxt->role[0] != 0)
-+		xasprintf(&micuser, "%s/%s", authctxt->user, authctxt->role);
-+	else
-+#endif
-+		micuser = authctxt->user;
-+	ssh_gssapi_buildmic(b, micuser, authctxt->service,
- 	    "gssapi-with-mic", ssh->kex->session_id);
- 
- 	if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
-@@ -313,6 +320,8 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
- 		logit("GSSAPI MIC check failed");
- 
- 	sshbuf_free(b);
-+	if (micuser != authctxt->user)
-+		free(micuser);
- 	free(mic.value);
- 
- 	authctxt->postponed = 0;
-diff --git a/auth2-hostbased.c b/auth2-hostbased.c
-index 8a1acdec3..a287091ec 100644
---- a/auth2-hostbased.c
-+++ b/auth2-hostbased.c
-@@ -130,7 +130,16 @@ userauth_hostbased(struct ssh *ssh, const char *method)
- 	/* reconstruct packet */
- 	if ((r = sshbuf_put_stringb(b, ssh->kex->session_id)) != 0 ||
- 	    (r = sshbuf_put_u8(b, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
-+#ifdef WITH_SELINUX
-+	    (authctxt->role
-+	    ? ( (r = sshbuf_put_u32(b, strlen(authctxt->user)+strlen(authctxt->role)+1)) != 0 ||
-+	        (r = sshbuf_put(b, authctxt->user, strlen(authctxt->user))) != 0 ||
-+	        (r = sshbuf_put_u8(b, '/') != 0) ||
-+	        (r = sshbuf_put(b, authctxt->role, strlen(authctxt->role))) != 0)
-+	    : (r = sshbuf_put_cstring(b, authctxt->user)) != 0) ||
-+#else
- 	    (r = sshbuf_put_cstring(b, authctxt->user)) != 0 ||
-+#endif
- 	    (r = sshbuf_put_cstring(b, authctxt->service)) != 0 ||
- 	    (r = sshbuf_put_cstring(b, method)) != 0 ||
- 	    (r = sshbuf_put_string(b, pkalg, alen)) != 0 ||
-diff --git a/auth2-pubkey.c b/auth2-pubkey.c
-index e446ef412..319e42b2b 100644
---- a/auth2-pubkey.c
-+++ b/auth2-pubkey.c
-@@ -71,6 +71,8 @@
- 
- /* import */
- extern ServerOptions options;
-+extern int inetd_flag;
-+extern Authctxt *the_authctxt;
- extern struct authmethod_cfg methodcfg_pubkey;
- 
- static char *
-@@ -201,9 +203,16 @@ userauth_pubkey(struct ssh *ssh, const char *method)
- 			goto done;
- 		}
- 		/* reconstruct packet */
--		xasprintf(&userstyle, "%s%s%s", authctxt->user,
-+		xasprintf(&userstyle, "%s%s%s%s%s", authctxt->user,
- 		    authctxt->style ? ":" : "",
--		    authctxt->style ? authctxt->style : "");
-+		    authctxt->style ? authctxt->style : "",
-+#ifdef WITH_SELINUX
-+		    authctxt->role ? "/" : "",
-+		    authctxt->role ? authctxt->role : ""
-+#else
-+		    "", ""
-+#endif
-+		    );
- 		if ((r = sshbuf_put_u8(b, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
- 		    (r = sshbuf_put_cstring(b, userstyle)) != 0 ||
- 		    (r = sshbuf_put_cstring(b, authctxt->service)) != 0 ||
-@@ -473,7 +482,8 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key,
- 	if ((pid = subprocess("AuthorizedPrincipalsCommand", command,
- 	    ac, av, &f,
- 	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
--	    runas_pw, temporarily_use_uid, restore_uid)) == 0)
-+	    runas_pw, temporarily_use_uid, restore_uid,
-+	    inetd_flag, the_authctxt)) == 0)
- 		goto out;
- 
- 	uid_swapped = 1;
-@@ -749,7 +759,8 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key,
- 	if ((pid = subprocess("AuthorizedKeysCommand", command,
- 	    ac, av, &f,
- 	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
--	    runas_pw, temporarily_use_uid, restore_uid)) == 0)
-+	    runas_pw, temporarily_use_uid, restore_uid,
-+	    inetd_flag, the_authctxt)) == 0)
- 		goto out;
- 
- 	uid_swapped = 1;
-diff --git a/auth2.c b/auth2.c
-index 3a1682746..bfd425eab 100644
---- a/auth2.c
-+++ b/auth2.c
-@@ -271,6 +271,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
- 	Authctxt *authctxt = ssh->authctxt;
- 	Authmethod *m = NULL;
- 	char *user = NULL, *service = NULL, *method = NULL, *style = NULL;
-+#ifdef WITH_SELINUX
-+	char *role = NULL;
-+#endif
- 	int r, authenticated = 0;
- 	double tstart = monotime_double();
- 
-@@ -284,6 +287,11 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
- 	debug("userauth-request for user %s service %s method %s", user, service, method);
- 	debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
- 
-+#ifdef WITH_SELINUX
-+	if ((role = strchr(user, '/')) != NULL)
-+		*role++ = 0;
-+#endif
-+
- 	if ((style = strchr(user, ':')) != NULL)
- 		*style++ = 0;
- 
-@@ -295,6 +303,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
- 		authctxt->user = xstrdup(user);
- 		authctxt->service = xstrdup(service);
- 		authctxt->style = style ? xstrdup(style) : NULL;
-+#ifdef WITH_SELINUX
-+		authctxt->role = role ? xstrdup(role) : NULL;
-+#endif
- 		if (authctxt->pw && strcmp(service, "ssh-connection")==0) {
- 			authctxt->valid = 1;
- 			debug2_f("setting up authctxt for %s", user);
-@@ -314,6 +325,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
- 		    authctxt->valid ? "authenticating " : "invalid ", user);
- 		setproctitle("%s [net]", authctxt->valid ? user : "unknown");
- 		mm_inform_authserv(service, style);
-+#ifdef WITH_SELINUX
-+         	mm_inform_authrole(role);
-+#endif
- 		userauth_banner(ssh);
- 		if ((r = kex_server_update_ext_info(ssh)) != 0)
- 			fatal_fr(r, "kex_server_update_ext_info failed");
-diff --git a/misc.c b/misc.c
-index ed3e9d314..e96fa8d61 100644
---- a/misc.c
-+++ b/misc.c
-@@ -869,6 +869,7 @@ char *
- colon(char *cp)
- {
- 	int flag = 0;
-+	int start = 1;
- 
- 	if (*cp == ':')		/* Leading colon is part of file name. */
- 		return NULL;
-@@ -884,6 +885,13 @@ colon(char *cp)
- 			return (cp);
- 		if (*cp == '/')
- 			return NULL;
-+		if (start) {
-+		/* Slash on beginning or after dots only denotes file name. */
-+			if (*cp == '/')
-+				return (0);
-+			if (*cp != '.')
-+				start = 0;
-+		}
- 	}
- 	return NULL;
- }
-@@ -2828,7 +2836,8 @@ stdfd_devnull(int do_stdin, int do_stdout, int do_stderr)
- pid_t
- subprocess(const char *tag, const char *command,
-     int ac, char **av, FILE **child, u_int flags,
--    struct passwd *pw, privdrop_fn *drop_privs, privrestore_fn *restore_privs)
-+    struct passwd *pw, privdrop_fn *drop_privs,
-+    privrestore_fn *restore_privs, int inetd, void *the_authctxt)
- {
- 	FILE *f = NULL;
- 	struct stat st;
-@@ -2961,6 +2970,13 @@ subprocess(const char *tag, const char *command,
- 			error("%s: dup2: %s", tag, strerror(errno));
- 			_exit(1);
- 		}
-+#ifdef WITH_SELINUX
-+		if (sshd_selinux_setup_env_variables(inetd, the_authctxt) < 0) {
-+			error ("failed to copy environment:  %s",
-+			    strerror(errno));
-+			_exit(127);
-+		}
-+#endif
- 		if (env != NULL)
- 			execve(av[0], av, env);
- 		else
-diff --git a/monitor.c b/monitor.c
-index 9d6672bb6..1e41dfa53 100644
---- a/monitor.c
-+++ b/monitor.c
-@@ -111,6 +111,9 @@ int mm_answer_sign(struct ssh *, int, struct sshbuf *);
- int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *);
- int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *);
- int mm_answer_authserv(struct ssh *, int, struct sshbuf *);
-+#ifdef WITH_SELINUX
-+int mm_answer_authrole(struct ssh *, int, struct sshbuf *);
-+#endif
- int mm_answer_authpassword(struct ssh *, int, struct sshbuf *);
- int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *);
- int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *);
-@@ -188,6 +191,9 @@ struct mon_table mon_dispatch_proto20[] = {
-     {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign},
-     {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow},
-     {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv},
-+#ifdef WITH_SELINUX
-+    {MONITOR_REQ_AUTHROLE, MON_ONCE, mm_answer_authrole},
-+#endif
-     {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner},
-     {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword},
- #ifdef USE_PAM
-@@ -946,6 +952,9 @@ mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m)
- 
- 	/* Allow service/style information on the auth context */
- 	monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1);
-+#ifdef WITH_SELINUX
-+	monitor_permit(mon_dispatch, MONITOR_REQ_AUTHROLE, 1);
-+#endif
- 	monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1);
- 
- #ifdef USE_PAM
-@@ -1020,6 +1029,26 @@ key_base_type_match(const char *method, const struct sshkey *key,
- 	return found;
- }
- 
-+#ifdef WITH_SELINUX
-+int
-+mm_answer_authrole(struct ssh *ssh, int sock, struct sshbuf *m)
-+{
-+	int r;
-+	monitor_permit_authentications(1);
-+
-+	if ((r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0)
-+		fatal_f("buffer error: %s", ssh_err(r));
-+	debug3_f("role=%s", authctxt->role);
-+
-+	if (strlen(authctxt->role) == 0) {
-+		free(authctxt->role);
-+		authctxt->role = NULL;
-+	}
-+
-+	return (0);
-+}
-+#endif
-+
- int
- mm_answer_authpassword(struct ssh *ssh, int sock, struct sshbuf *m)
- {
-@@ -1392,7 +1421,7 @@ monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
- 	struct sshbuf *b;
- 	struct sshkey *hostkey = NULL;
- 	const u_char *p;
--	char *userstyle, *cp;
-+	char *userstyle, *s, *cp;
- 	size_t len;
- 	u_char type;
- 	int hostbound = 0, r, fail = 0;
-@@ -1423,6 +1452,8 @@ monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
- 		fail++;
- 	if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
- 		fatal_fr(r, "parse userstyle");
-+	if ((s = strchr(cp, '/')) != NULL)
-+		*s = '\0';
- 	xasprintf(&userstyle, "%s%s%s", authctxt->user,
- 	    authctxt->style ? ":" : "",
- 	    authctxt->style ? authctxt->style : "");
-@@ -1473,7 +1504,7 @@ monitor_valid_hostbasedblob(const u_char *data, u_int datalen,
- {
- 	struct sshbuf *b;
- 	const u_char *p;
--	char *cp, *userstyle;
-+	char *cp, *s, *userstyle;
- 	size_t len;
- 	int r, fail = 0;
- 	u_char type;
-@@ -1494,6 +1525,8 @@ monitor_valid_hostbasedblob(const u_char *data, u_int datalen,
- 		fail++;
- 	if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
- 		fatal_fr(r, "parse userstyle");
-+	if ((s = strchr(cp, '/')) != NULL)
-+		*s = '\0';
- 	xasprintf(&userstyle, "%s%s%s", authctxt->user,
- 	    authctxt->style ? ":" : "",
- 	    authctxt->style ? authctxt->style : "");
-diff --git a/monitor.h b/monitor.h
-index fe0b00b2e..1b46e794e 100644
---- a/monitor.h
-+++ b/monitor.h
-@@ -57,6 +57,10 @@ enum monitor_reqtype {
- 	MONITOR_REQ_TERM = 50,
- 	MONITOR_REQ_STATE = 51, MONITOR_ANS_STATE = 52,
- 
-+#ifdef WITH_SELINUX
-+	MONITOR_REQ_AUTHROLE = 80,
-+#endif
-+
- 	MONITOR_REQ_PAM_START = 100,
- 	MONITOR_REQ_PAM_ACCOUNT = 102, MONITOR_ANS_PAM_ACCOUNT = 103,
- 	MONITOR_REQ_PAM_INIT_CTX = 104, MONITOR_ANS_PAM_INIT_CTX = 105,
-diff --git a/monitor_wrap.c b/monitor_wrap.c
-index 81596a4cc..f3c341bc7 100644
---- a/monitor_wrap.c
-+++ b/monitor_wrap.c
-@@ -473,6 +473,27 @@ mm_inform_authserv(char *service, char *style)
- 	sshbuf_free(m);
- }
- 
-+/* Inform the privileged process about role */
-+
-+#ifdef WITH_SELINUX
-+void
-+mm_inform_authrole(char *role)
-+{
-+	int r;
-+	struct sshbuf *m;
-+
-+	debug3_f("entering");
-+
-+	if ((m = sshbuf_new()) == NULL)
-+		fatal_f("sshbuf_new failed");
-+	if ((r = sshbuf_put_cstring(m, role ? role : "")) != 0)
-+		fatal_f("buffer error: %s", ssh_err(r));
-+	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHROLE, m);
-+
-+	sshbuf_free(m);
-+}
-+#endif
-+
- /* Do the password authentication */
- int
- mm_auth_password(struct ssh *ssh, char *password)
-diff --git a/monitor_wrap.h b/monitor_wrap.h
-index c2f7f97d9..3e08ddd78 100644
---- a/monitor_wrap.h
-+++ b/monitor_wrap.h
-@@ -51,6 +51,9 @@ int mm_sshkey_sign(struct ssh *, struct sshkey *, u_char **, size_t *,
-     const u_char *, size_t, const char *, const char *,
-     const char *, u_int compat);
- void mm_inform_authserv(char *, char *);
-+#ifdef WITH_SELINUX
-+void mm_inform_authrole(char *);
-+#endif
- struct passwd *mm_getpwnamallow(struct ssh *, const char *);
- char *mm_auth2_read_banner(void);
- int mm_auth_password(struct ssh *, char *);
--- 
-2.53.0
-

diff --git a/0001-upstream-fix-GSSAPI-option-names-that-I-somehow-scre.patch b/0001-upstream-fix-GSSAPI-option-names-that-I-somehow-scre.patch
new file mode 100644
index 0000000..21e2940
--- /dev/null
+++ b/0001-upstream-fix-GSSAPI-option-names-that-I-somehow-scre.patch
@@ -0,0 +1,101 @@
+From 823ad00d14065ab932794be8d0a75a86b6277849 Mon Sep 17 00:00:00 2001
+From: "djm@openbsd.org" <djm@openbsd.org>
+Date: Tue, 7 Jul 2026 01:00:22 +0000
+Subject: [PATCH 01/54] upstream: fix GSSAPI option names, that I somehow
+ screwed up while
+
+refactoring servconf.c bz3974 patch from Colin Watson
+
+OpenBSD-Commit-ID: be39ad3dbe36d9ecdb86f3811da5dfbdc9bcb1e6
+---
+ servconf.c | 18 +++++++++---------
+ servconf.h | 18 +++++++++---------
+ 2 files changed, 18 insertions(+), 18 deletions(-)
+
+diff --git a/servconf.c b/servconf.c
+index ce388f1dd..9b443bea0 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: servconf.c,v 1.450 2026/06/29 08:59:31 djm Exp $ */
++/* $OpenBSD: servconf.c,v 1.451 2026/07/07 01:00:22 djm Exp $ */
+ /*
+  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+  *                    All rights reserved
+@@ -1433,19 +1433,19 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ #endif /* KRB5 */
+ 
+ #ifdef GSSAPI
+-	case sGssAuthentication:
++	case sGSSAPIAuthentication:
+ 		intptr = &options->gss_authentication;
+ 		goto parse_flag;
+ 
+-	case sGssCleanupCreds:
++	case sGSSAPICleanupCredentials:
+ 		intptr = &options->gss_cleanup_creds;
+ 		goto parse_flag;
+ 
+-	case sGssDelegateCreds:
++	case sGSSAPIDelegateCredentials:
+ 		intptr = &options->gss_deleg_creds;
+ 		goto parse_flag;
+ 
+-	case sGssStrictAcceptor:
++	case sGSSAPIStrictAcceptorCheck:
+ 		intptr = &options->gss_strict_acceptor;
+ 		goto parse_flag;
+ #endif /* GSSAPI */
+@@ -4215,10 +4215,10 @@ dump_config(ServerOptions *o)
+ # endif
+ #endif
+ #ifdef GSSAPI
+-	dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+-	dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds);
+-	dump_cfg_fmtint(sGssDelegateCreds, o->gss_deleg_creds);
+-	dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor);
++	dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
++	dump_cfg_fmtint(sGSSAPICleanupCredentials, o->gss_cleanup_creds);
++	dump_cfg_fmtint(sGSSAPIDelegateCredentials, o->gss_deleg_creds);
++	dump_cfg_fmtint(sGSSAPIStrictAcceptorCheck, o->gss_strict_acceptor);
+ #endif
+ 	dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
+ 	dump_cfg_fmtint(sKbdInteractiveAuthentication,
+diff --git a/servconf.h b/servconf.h
+index 9e64e4673..a2345e88a 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: servconf.h,v 1.177 2026/05/31 11:30:50 djm Exp $ */
++/* $OpenBSD: servconf.h,v 1.179 2026/07/07 01:00:22 djm Exp $ */
+ 
+ /*
+  * Author: Tatu Ylonen <ylo@cs.hut.fi>
+@@ -314,16 +314,16 @@ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOB
+ 
+ #ifdef GSSAPI
+ #define SSHD_CONFIG_ENTRIES_GSS \
+-SSHCONF_INTFLAG(gss_authentication, GssAuthentication, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+-SSHCONF_INTFLAG(gss_cleanup_creds, GssCleanupCreds, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+-SSHCONF_INTFLAG(gss_deleg_creds, GssDelegateCreds, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+-SSHCONF_INTFLAG(gss_strict_acceptor, GssStrictAcceptor, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE)
++SSHCONF_INTFLAG(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_INTFLAG(gss_cleanup_creds, GSSAPICleanupCredentials, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE)
+ #else /* GSSAPI */
+ #define SSHD_CONFIG_ENTRIES_GSS \
+-SSHCONF_UNSUPPORTED_INT(gss_authentication, GssAuthentication, SSHCFG_ALL) \
+-SSHCONF_UNSUPPORTED_INT(gss_cleanup_creds, GssCleanupCreds, SSHCFG_GLOBAL) \
+-SSHCONF_UNSUPPORTED_INT(gss_deleg_creds, GssDelegateCreds, SSHCFG_GLOBAL) \
+-SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GssStrictAcceptor, SSHCFG_GLOBAL)
++SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
++SSHCONF_UNSUPPORTED_INT(gss_cleanup_creds, GSSAPICleanupCredentials, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL)
+ #endif /* GSSAPI */
+ 
+ #define SSHD_CONFIG_ENTRIES \
+-- 
+2.55.0
+

diff --git a/0002-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch b/0002-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
new file mode 100644
index 0000000..d68620a
--- /dev/null
+++ b/0002-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
@@ -0,0 +1,416 @@
+From 0ff113d55dffee623a622e081b426e5b7e57c571 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 13 Apr 2026 14:06:32 +0200
+Subject: [PATCH 02/54] Add SELinux role and MLS (Multi-Level Security) support
+
+openssh-7.8p1-role-mls
+
+Add support for SELinux role-based access control and Multi-Level
+Security (MLS) by allowing users to specify their role as part of the
+username in the format user/role.
+
+Changes:
+- auth.h: Add role field to Authctxt structure
+- auth2.c: Parse role from username and inform monitor process
+- auth2-gss.c: Include role in GSSAPI MIC authentication
+- auth2-hostbased.c: Include role in hostbased authentication signature
+- auth2-pubkey.c: Include role in pubkey authentication userstyle
+- monitor.c: Add mm_answer_authrole() and handle role in authentication
+  blob validation
+- monitor_wrap.c: Add mm_inform_authrole() for privilege separation
+- misc.c: Update colon() to handle /role syntax in file paths
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ auth.h            |  3 +++
+ auth2-gss.c       | 11 ++++++++++-
+ auth2-hostbased.c |  9 +++++++++
+ auth2-pubkey.c    | 19 +++++++++++++++----
+ auth2.c           | 14 ++++++++++++++
+ misc.c            | 18 +++++++++++++++++-
+ monitor.c         | 37 +++++++++++++++++++++++++++++++++++--
+ monitor.h         |  4 ++++
+ monitor_wrap.c    | 21 +++++++++++++++++++++
+ monitor_wrap.h    |  3 +++
+ 10 files changed, 131 insertions(+), 8 deletions(-)
+
+diff --git a/auth.h b/auth.h
+index 0f11458ca..efe819323 100644
+--- a/auth.h
++++ b/auth.h
+@@ -65,6 +65,9 @@ struct Authctxt {
+ 	char		*service;
+ 	struct passwd	*pw;		/* set if 'valid' */
+ 	char		*style;
++#ifdef WITH_SELINUX
++	char		*role;
++#endif
+ 
+ 	/* Method lists for multiple authentication */
+ 	char		**auth_methods;	/* modified from server config */
+diff --git a/auth2-gss.c b/auth2-gss.c
+index 85251b7d0..dfee5613e 100644
+--- a/auth2-gss.c
++++ b/auth2-gss.c
+@@ -282,6 +282,7 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
+ 	Authctxt *authctxt = ssh->authctxt;
+ 	Gssctxt *gssctxt;
+ 	int r, authenticated = 0;
++	char *micuser;
+ 	struct sshbuf *b;
+ 	gss_buffer_desc mic, gssbuf;
+ 	u_char *p;
+@@ -299,7 +300,13 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
+ 		fatal_f("sshbuf_new failed");
+ 	mic.value = p;
+ 	mic.length = len;
+-	ssh_gssapi_buildmic(b, authctxt->user, authctxt->service,
++#ifdef WITH_SELINUX
++	if (authctxt->role && authctxt->role[0] != 0)
++		xasprintf(&micuser, "%s/%s", authctxt->user, authctxt->role);
++	else
++#endif
++		micuser = authctxt->user;
++	ssh_gssapi_buildmic(b, micuser, authctxt->service,
+ 	    "gssapi-with-mic", ssh->kex->session_id);
+ 
+ 	if ((gssbuf.value = sshbuf_mutable_ptr(b)) == NULL)
+@@ -312,6 +319,8 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
+ 		logit("GSSAPI MIC check failed");
+ 
+ 	sshbuf_free(b);
++	if (micuser != authctxt->user)
++		free(micuser);
+ 	free(mic.value);
+ 
+ 	if (!authenticated)
+diff --git a/auth2-hostbased.c b/auth2-hostbased.c
+index 8a1acdec3..a287091ec 100644
+--- a/auth2-hostbased.c
++++ b/auth2-hostbased.c
+@@ -130,7 +130,16 @@ userauth_hostbased(struct ssh *ssh, const char *method)
+ 	/* reconstruct packet */
+ 	if ((r = sshbuf_put_stringb(b, ssh->kex->session_id)) != 0 ||
+ 	    (r = sshbuf_put_u8(b, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
++#ifdef WITH_SELINUX
++	    (authctxt->role
++	    ? ( (r = sshbuf_put_u32(b, strlen(authctxt->user)+strlen(authctxt->role)+1)) != 0 ||
++	        (r = sshbuf_put(b, authctxt->user, strlen(authctxt->user))) != 0 ||
++	        (r = sshbuf_put_u8(b, '/') != 0) ||
++	        (r = sshbuf_put(b, authctxt->role, strlen(authctxt->role))) != 0)
++	    : (r = sshbuf_put_cstring(b, authctxt->user)) != 0) ||
++#else
+ 	    (r = sshbuf_put_cstring(b, authctxt->user)) != 0 ||
++#endif
+ 	    (r = sshbuf_put_cstring(b, authctxt->service)) != 0 ||
+ 	    (r = sshbuf_put_cstring(b, method)) != 0 ||
+ 	    (r = sshbuf_put_string(b, pkalg, alen)) != 0 ||
+diff --git a/auth2-pubkey.c b/auth2-pubkey.c
+index e446ef412..319e42b2b 100644
+--- a/auth2-pubkey.c
++++ b/auth2-pubkey.c
+@@ -71,6 +71,8 @@
+ 
+ /* import */
+ extern ServerOptions options;
++extern int inetd_flag;
++extern Authctxt *the_authctxt;
+ extern struct authmethod_cfg methodcfg_pubkey;
+ 
+ static char *
+@@ -201,9 +203,16 @@ userauth_pubkey(struct ssh *ssh, const char *method)
+ 			goto done;
+ 		}
+ 		/* reconstruct packet */
+-		xasprintf(&userstyle, "%s%s%s", authctxt->user,
++		xasprintf(&userstyle, "%s%s%s%s%s", authctxt->user,
+ 		    authctxt->style ? ":" : "",
+-		    authctxt->style ? authctxt->style : "");
++		    authctxt->style ? authctxt->style : "",
++#ifdef WITH_SELINUX
++		    authctxt->role ? "/" : "",
++		    authctxt->role ? authctxt->role : ""
++#else
++		    "", ""
++#endif
++		    );
+ 		if ((r = sshbuf_put_u8(b, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
+ 		    (r = sshbuf_put_cstring(b, userstyle)) != 0 ||
+ 		    (r = sshbuf_put_cstring(b, authctxt->service)) != 0 ||
+@@ -473,7 +482,8 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key,
+ 	if ((pid = subprocess("AuthorizedPrincipalsCommand", command,
+ 	    ac, av, &f,
+ 	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
+-	    runas_pw, temporarily_use_uid, restore_uid)) == 0)
++	    runas_pw, temporarily_use_uid, restore_uid,
++	    inetd_flag, the_authctxt)) == 0)
+ 		goto out;
+ 
+ 	uid_swapped = 1;
+@@ -749,7 +759,8 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key,
+ 	if ((pid = subprocess("AuthorizedKeysCommand", command,
+ 	    ac, av, &f,
+ 	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD,
+-	    runas_pw, temporarily_use_uid, restore_uid)) == 0)
++	    runas_pw, temporarily_use_uid, restore_uid,
++	    inetd_flag, the_authctxt)) == 0)
+ 		goto out;
+ 
+ 	uid_swapped = 1;
+diff --git a/auth2.c b/auth2.c
+index 3f353a719..497fa8ed2 100644
+--- a/auth2.c
++++ b/auth2.c
+@@ -277,6 +277,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+ 	Authctxt *authctxt = ssh->authctxt;
+ 	Authmethod *m = NULL;
+ 	char *user = NULL, *service = NULL, *method = NULL, *style = NULL;
++#ifdef WITH_SELINUX
++	char *role = NULL;
++#endif
+ 	int r, authenticated = 0;
+ 	double tstart = monotime_double();
+ 
+@@ -290,6 +293,11 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+ 	debug("userauth-request for user %s service %s method %s", user, service, method);
+ 	debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
+ 
++#ifdef WITH_SELINUX
++	if ((role = strchr(user, '/')) != NULL)
++		*role++ = 0;
++#endif
++
+ 	if ((style = strchr(user, ':')) != NULL)
+ 		*style++ = 0;
+ 
+@@ -301,6 +309,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+ 		authctxt->user = xstrdup(user);
+ 		authctxt->service = xstrdup(service);
+ 		authctxt->style = style ? xstrdup(style) : NULL;
++#ifdef WITH_SELINUX
++		authctxt->role = role ? xstrdup(role) : NULL;
++#endif
+ 		if (authctxt->pw && strcmp(service, "ssh-connection")==0) {
+ 			authctxt->valid = 1;
+ 			debug2_f("setting up authctxt for %s", user);
+@@ -320,6 +331,9 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+ 		    authctxt->valid ? "authenticating " : "invalid ", user);
+ 		setproctitle("%s [net]", authctxt->valid ? user : "unknown");
+ 		mm_inform_authserv(service, style);
++#ifdef WITH_SELINUX
++         	mm_inform_authrole(role);
++#endif
+ 		userauth_banner(ssh);
+ 		if ((r = kex_server_update_ext_info(ssh)) != 0)
+ 			fatal_fr(r, "kex_server_update_ext_info failed");
+diff --git a/misc.c b/misc.c
+index 517fa7a97..c0e16437a 100644
+--- a/misc.c
++++ b/misc.c
+@@ -869,6 +869,7 @@ char *
+ colon(char *cp)
+ {
+ 	int flag = 0;
++	int start = 1;
+ 
+ 	if (*cp == ':')		/* Leading colon is part of file name. */
+ 		return NULL;
+@@ -884,6 +885,13 @@ colon(char *cp)
+ 			return (cp);
+ 		if (*cp == '/')
+ 			return NULL;
++		if (start) {
++		/* Slash on beginning or after dots only denotes file name. */
++			if (*cp == '/')
++				return (0);
++			if (*cp != '.')
++				start = 0;
++		}
+ 	}
+ 	return NULL;
+ }
+@@ -2830,7 +2838,8 @@ stdfd_devnull(int do_stdin, int do_stdout, int do_stderr)
+ pid_t
+ subprocess(const char *tag, const char *command,
+     int ac, char **av, FILE **child, u_int flags,
+-    struct passwd *pw, privdrop_fn *drop_privs, privrestore_fn *restore_privs)
++    struct passwd *pw, privdrop_fn *drop_privs,
++    privrestore_fn *restore_privs, int inetd, void *the_authctxt)
+ {
+ 	FILE *f = NULL;
+ 	struct stat st;
+@@ -2963,6 +2972,13 @@ subprocess(const char *tag, const char *command,
+ 			error("%s: dup2: %s", tag, strerror(errno));
+ 			_exit(1);
+ 		}
++#ifdef WITH_SELINUX
++		if (sshd_selinux_setup_env_variables(inetd, the_authctxt) < 0) {
++			error ("failed to copy environment:  %s",
++			    strerror(errno));
++			_exit(127);
++		}
++#endif
+ 		if (env != NULL)
+ 			execve(av[0], av, env);
+ 		else
+diff --git a/monitor.c b/monitor.c
+index 73a85408e..6fe9f46fc 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -111,6 +111,9 @@ int mm_answer_sign(struct ssh *, int, struct sshbuf *);
+ int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *);
+ int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *);
+ int mm_answer_authserv(struct ssh *, int, struct sshbuf *);
++#ifdef WITH_SELINUX
++int mm_answer_authrole(struct ssh *, int, struct sshbuf *);
++#endif
+ int mm_answer_authpassword(struct ssh *, int, struct sshbuf *);
+ int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *);
+ int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *);
+@@ -188,6 +191,9 @@ struct mon_table mon_dispatch_proto20[] = {
+     {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign},
+     {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow},
+     {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv},
++#ifdef WITH_SELINUX
++    {MONITOR_REQ_AUTHROLE, MON_ONCE, mm_answer_authrole},
++#endif
+     {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner},
+     {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword},
+ #ifdef USE_PAM
+@@ -920,6 +926,9 @@ mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m)
+ 
+ 	/* Allow service/style information on the auth context */
+ 	monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1);
++#ifdef WITH_SELINUX
++	monitor_permit(mon_dispatch, MONITOR_REQ_AUTHROLE, 1);
++#endif
+ 	monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1);
+ 
+ #ifdef USE_PAM
+@@ -994,6 +1003,26 @@ key_base_type_match(const char *method, const struct sshkey *key,
+ 	return found;
+ }
+ 
++#ifdef WITH_SELINUX
++int
++mm_answer_authrole(struct ssh *ssh, int sock, struct sshbuf *m)
++{
++	int r;
++	monitor_permit_authentications(1);
++
++	if ((r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0)
++		fatal_f("buffer error: %s", ssh_err(r));
++	debug3_f("role=%s", authctxt->role);
++
++	if (strlen(authctxt->role) == 0) {
++		free(authctxt->role);
++		authctxt->role = NULL;
++	}
++
++	return (0);
++}
++#endif
++
+ int
+ mm_answer_authpassword(struct ssh *ssh, int sock, struct sshbuf *m)
+ {
+@@ -1366,7 +1395,7 @@ monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
+ 	struct sshbuf *b;
+ 	struct sshkey *hostkey = NULL;
+ 	const u_char *p;
+-	char *userstyle, *cp;
++	char *userstyle, *s, *cp;
+ 	size_t len;
+ 	u_char type;
+ 	int hostbound = 0, r, fail = 0;
+@@ -1397,6 +1426,8 @@ monitor_valid_userblob(struct ssh *ssh, const u_char *data, u_int datalen)
+ 		fail++;
+ 	if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
+ 		fatal_fr(r, "parse userstyle");
++	if ((s = strchr(cp, '/')) != NULL)
++		*s = '\0';
+ 	xasprintf(&userstyle, "%s%s%s", authctxt->user,
+ 	    authctxt->style ? ":" : "",
+ 	    authctxt->style ? authctxt->style : "");
+@@ -1447,7 +1478,7 @@ monitor_valid_hostbasedblob(const u_char *data, u_int datalen,
+ {
+ 	struct sshbuf *b;
+ 	const u_char *p;
+-	char *cp, *userstyle;
++	char *cp, *s, *userstyle;
+ 	size_t len;
+ 	int r, fail = 0;
+ 	u_char type;
+@@ -1468,6 +1499,8 @@ monitor_valid_hostbasedblob(const u_char *data, u_int datalen,
+ 		fail++;
+ 	if ((r = sshbuf_get_cstring(b, &cp, NULL)) != 0)
+ 		fatal_fr(r, "parse userstyle");
++	if ((s = strchr(cp, '/')) != NULL)
++		*s = '\0';
+ 	xasprintf(&userstyle, "%s%s%s", authctxt->user,
+ 	    authctxt->style ? ":" : "",
+ 	    authctxt->style ? authctxt->style : "");
+diff --git a/monitor.h b/monitor.h
+index fe0b00b2e..1b46e794e 100644
+--- a/monitor.h
++++ b/monitor.h
+@@ -57,6 +57,10 @@ enum monitor_reqtype {
+ 	MONITOR_REQ_TERM = 50,
+ 	MONITOR_REQ_STATE = 51, MONITOR_ANS_STATE = 52,
+ 
++#ifdef WITH_SELINUX
++	MONITOR_REQ_AUTHROLE = 80,
++#endif
++
+ 	MONITOR_REQ_PAM_START = 100,
+ 	MONITOR_REQ_PAM_ACCOUNT = 102, MONITOR_ANS_PAM_ACCOUNT = 103,
+ 	MONITOR_REQ_PAM_INIT_CTX = 104, MONITOR_ANS_PAM_INIT_CTX = 105,
+diff --git a/monitor_wrap.c b/monitor_wrap.c
+index e2b9a2802..b5aeb8b2c 100644
+--- a/monitor_wrap.c
++++ b/monitor_wrap.c
+@@ -438,6 +438,27 @@ mm_inform_authserv(char *service, char *style)
+ 	sshbuf_free(m);
+ }
+ 
++/* Inform the privileged process about role */
++
++#ifdef WITH_SELINUX
++void
++mm_inform_authrole(char *role)
++{
++	int r;
++	struct sshbuf *m;
++
++	debug3_f("entering");
++
++	if ((m = sshbuf_new()) == NULL)
++		fatal_f("sshbuf_new failed");
++	if ((r = sshbuf_put_cstring(m, role ? role : "")) != 0)
++		fatal_f("buffer error: %s", ssh_err(r));
++	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHROLE, m);
++
++	sshbuf_free(m);
++}
++#endif
++
+ /* Do the password authentication */
+ int
+ mm_auth_password(struct ssh *ssh, char *password)
+diff --git a/monitor_wrap.h b/monitor_wrap.h
+index f43759b21..30585187c 100644
+--- a/monitor_wrap.h
++++ b/monitor_wrap.h
+@@ -51,6 +51,9 @@ int mm_sshkey_sign(struct ssh *, struct sshkey *, u_char **, size_t *,
+     const u_char *, size_t, const char *, const char *,
+     const char *, u_int compat);
+ void mm_inform_authserv(char *, char *);
++#ifdef WITH_SELINUX
++void mm_inform_authrole(char *);
++#endif
+ struct passwd *mm_getpwnamallow(struct ssh *, const char *);
+ char *mm_auth2_read_banner(void);
+ int mm_auth_password(struct ssh *, char *);
+-- 
+2.55.0
+

diff --git a/0002-Implement-SELinux-environment-variable-setup-for-sub.patch b/0002-Implement-SELinux-environment-variable-setup-for-sub.patch
deleted file mode 100644
index e54ff03..0000000
--- a/0002-Implement-SELinux-environment-variable-setup-for-sub.patch
+++ /dev/null
@@ -1,677 +0,0 @@
-From 8b25614132c78116498a2b94634b2b8bacf82f5f Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 13 Apr 2026 14:06:47 +0200
-Subject: [PATCH 02/54] Implement SELinux environment variable setup for
- subprocess execution
-
-openssh-6.6p1-selinux-env-setup
-
-Implement comprehensive SELinux context management for sshd, including
-role selection, MLS level validation, and environment variable setup for
-pam_selinux integration.
-
-Changes:
-- openbsd-compat/port-linux-sshd.c: New file with SELinux context
-  management functions including sshd_selinux_getctxbyname(),
-  sshd_selinux_setup_exec_context(), and environment variable setup
-- openbsd-compat/Makefile.in: Add port-linux-sshd.o to build
-- openbsd-compat/port-linux.c: Remove old ssh_selinux_setup_exec_context(),
-  update ssh_selinux_setup_pty() to use getexeccon()
-- openbsd-compat/port-linux.h: Update function declarations for
-  sshd-specific SELinux functions
-- platform.c: Replace ssh_selinux_setup_exec_context() with
-  sshd_selinux_setup_exec_context() and pass additional parameters
-  (inetd_flag, do_pam_putenv, the_authctxt, options.use_pam)
-- sshd-session.c: Add SELinux execution context setup call in main()
-- auth-pam.c: Change do_pam_putenv() value parameter to const char *
-- auth-pam.h: Update do_pam_putenv() signature and add do_pam_chauthtok()
-  declaration
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- auth-pam.c                       |   2 +-
- auth-pam.h                       |   3 +-
- openbsd-compat/Makefile.in       |   3 +-
- openbsd-compat/port-linux-sshd.c | 454 +++++++++++++++++++++++++++++++
- openbsd-compat/port-linux.c      |  37 +--
- openbsd-compat/port-linux.h      |   5 +-
- platform.c                       |   8 +-
- sshd-session.c                   |   7 +-
- 8 files changed, 480 insertions(+), 39 deletions(-)
- create mode 100644 openbsd-compat/port-linux-sshd.c
-
-diff --git a/auth-pam.c b/auth-pam.c
-index 29607e041..3f342f9e1 100644
---- a/auth-pam.c
-+++ b/auth-pam.c
-@@ -1145,7 +1145,7 @@ is_pam_session_open(void)
-  * during the ssh authentication process.
-  */
- int
--do_pam_putenv(char *name, char *value)
-+do_pam_putenv(char *name, const char *value)
- {
- 	int ret = 1;
- 	char *compound;
-diff --git a/auth-pam.h b/auth-pam.h
-index 491336701..f9167cfc8 100644
---- a/auth-pam.h
-+++ b/auth-pam.h
-@@ -32,7 +32,8 @@ void finish_pam(void);
- u_int do_pam_account(void);
- void do_pam_session(struct ssh *);
- void do_pam_setcred(void);
--int do_pam_putenv(char *, char *);
-+void do_pam_chauthtok(void);
-+int do_pam_putenv(char *, const char *);
- char ** fetch_pam_environment(void);
- char ** fetch_pam_child_environment(void);
- void free_pam_environment(char **);
-diff --git a/openbsd-compat/Makefile.in b/openbsd-compat/Makefile.in
-index 53c87db6d..39531ae77 100644
---- a/openbsd-compat/Makefile.in
-+++ b/openbsd-compat/Makefile.in
-@@ -102,7 +102,8 @@ PORTS=	port-aix.o \
- 	port-prngd.o \
- 	port-solaris.o \
- 	port-net.o \
--	port-uw.o
-+	port-uw.o \
-+	port-linux-sshd.o
- 
- .c.o:
- 	$(CC) $(CFLAGS_NOPIE) $(PICFLAG) $(CPPFLAGS) -c $<
-diff --git a/openbsd-compat/port-linux-sshd.c b/openbsd-compat/port-linux-sshd.c
-new file mode 100644
-index 000000000..ab083a637
---- /dev/null
-+++ b/openbsd-compat/port-linux-sshd.c
-@@ -0,0 +1,454 @@
-+/*
-+ * Copyright (c) 2005 Daniel Walsh <dwalsh@redhat.com>
-+ * Copyright (c) 2014 Petr Lautrbach <plautrba@redhat.com>
-+ *
-+ * 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.
-+ */
-+
-+/*
-+ * Linux-specific portability code - just SELinux support for sshd at present
-+ */
-+
-+#include "includes.h"
-+
-+#if defined(WITH_SELINUX) || defined(LINUX_OOM_ADJUST)
-+#include <errno.h>
-+#include <stdarg.h>
-+#include <string.h>
-+#include <stdio.h>
-+#include <stdlib.h>
-+
-+#include "log.h"
-+#include "xmalloc.h"
-+#include "misc.h"      /* servconf.h needs misc.h for struct ForwardOptions */
-+#include "servconf.h"
-+#include "port-linux.h"
-+#include "sshkey.h"
-+#include "hostfile.h"
-+#include "auth.h"
-+
-+#ifdef WITH_SELINUX
-+#include <selinux/selinux.h>
-+#include <selinux/context.h>
-+#include <selinux/get_context_list.h>
-+#include <selinux/get_default_type.h>
-+
-+#ifdef HAVE_LINUX_AUDIT
-+#include <libaudit.h>
-+#include <unistd.h>
-+#endif
-+
-+/* Wrapper around is_selinux_enabled() to log its return value once only */
-+int
-+sshd_selinux_enabled(void)
-+{
-+	static int enabled = -1;
-+
-+	if (enabled == -1) {
-+		enabled = (is_selinux_enabled() == 1);
-+		debug("SELinux support %s", enabled ? "enabled" : "disabled");
-+	}
-+
-+	return (enabled);
-+}
-+
-+/* Send audit message */
-+static int
-+sshd_selinux_send_audit_message(int success, security_context_t default_context,
-+		       security_context_t selected_context)
-+{
-+	int rc=0;
-+#ifdef HAVE_LINUX_AUDIT
-+	char *msg = NULL;
-+	int audit_fd = audit_open();
-+	security_context_t default_raw=NULL;
-+	security_context_t selected_raw=NULL;
-+	rc = -1;
-+	if (audit_fd < 0) {
-+		if (errno == EINVAL || errno == EPROTONOSUPPORT ||
-+					errno == EAFNOSUPPORT)
-+				return 0; /* No audit support in kernel */
-+		error("Error connecting to audit system.");
-+		return rc;
-+	}
-+	if (selinux_trans_to_raw_context(default_context, &default_raw) < 0) {
-+		error("Error translating default context.");
-+		default_raw = NULL;
-+	}
-+	if (selinux_trans_to_raw_context(selected_context, &selected_raw) < 0) {
-+		error("Error translating selected context.");
-+		selected_raw = NULL;
-+	}
-+	if (asprintf(&msg, "sshd: default-context=%s selected-context=%s",
-+		     default_raw ? default_raw : (default_context ? default_context: "?"),
-+		     selected_context ? selected_raw : (selected_context ? selected_context :"?")) < 0) {
-+		error("Error allocating memory.");
-+		goto out;
-+	}
-+	if (audit_log_user_message(audit_fd, AUDIT_USER_ROLE_CHANGE,
-+				   msg, NULL, NULL, NULL, success) <= 0) {
-+		error("Error sending audit message.");
-+		goto out;
-+	}
-+	rc = 0;
-+      out:
-+	free(msg);
-+	freecon(default_raw);
-+	freecon(selected_raw);
-+	close(audit_fd);
-+#endif
-+	return rc;
-+}
-+
-+static int
-+mls_range_allowed(security_context_t src, security_context_t dst)
-+{
-+	struct av_decision avd;
-+	int retval;
-+	access_vector_t bit;
-+	security_class_t class;
-+
-+	debug_f("src:%s dst:%s", src, dst);
-+	class = string_to_security_class("context");
-+	if (!class) {
-+		error("string_to_security_class failed to translate security class context");
-+		return 1;
-+	}
-+	bit = string_to_av_perm(class, "contains");
-+	if (!bit) {
-+		error("string_to_av_perm failed to translate av perm contains");
-+		return 1;
-+	}
-+	retval = security_compute_av(src, dst, class, bit, &avd);
-+	if (retval || ((bit & avd.allowed) != bit))
-+		return 0;
-+
-+	return 1;
-+}
-+
-+static int
-+get_user_context(const char *sename, const char *role, const char *lvl,
-+	security_context_t *sc) {
-+#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
-+	if (lvl == NULL || lvl[0] == '\0' || get_default_context_with_level(sename, lvl, NULL, sc) != 0) {
-+	        /* User may have requested a level completely outside of his 
-+	           allowed range. We get a context just for auditing as the
-+	           range check below will certainly fail for default context. */
-+#endif
-+		if (get_default_context(sename, NULL, sc) != 0) {
-+			*sc = NULL;
-+			return -1;
-+		}
-+#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
-+	}
-+#endif
-+	if (role != NULL && role[0]) {
-+		context_t con;
-+		char *type=NULL;
-+		if (get_default_type(role, &type) != 0) {
-+			error("get_default_type: failed to get default type for '%s'",
-+				role);
-+			goto out;
-+		}
-+		con = context_new(*sc);
-+		if (!con) {
-+			goto out;
-+		}
-+		context_role_set(con, role);
-+		context_type_set(con, type);
-+		freecon(*sc);
-+		*sc = strdup(context_str(con));
-+		context_free(con);
-+		if (!*sc)
-+			return -1;
-+	}
-+#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
-+	if (lvl != NULL && lvl[0]) {
-+		/* verify that the requested range is obtained */
-+		context_t con;
-+		security_context_t obtained_raw;
-+		security_context_t requested_raw;
-+		con = context_new(*sc);
-+		if (!con) {
-+			goto out;
-+		}
-+		context_range_set(con, lvl);
-+		if (selinux_trans_to_raw_context(*sc, &obtained_raw) < 0) {
-+			context_free(con);
-+			goto out;
-+		}
-+		if (selinux_trans_to_raw_context(context_str(con), &requested_raw) < 0) {
-+			freecon(obtained_raw);
-+			context_free(con);
-+			goto out;
-+		}
-+
-+		debug("get_user_context: obtained context '%s' requested context '%s'",
-+			obtained_raw, requested_raw);
-+		if (strcmp(obtained_raw, requested_raw)) {
-+			/* set the context to the real requested one but fail */
-+			freecon(requested_raw);
-+			freecon(obtained_raw);
-+			freecon(*sc);
-+			*sc = strdup(context_str(con));
-+			context_free(con);
-+			return -1;
-+		}
-+		freecon(requested_raw);
-+		freecon(obtained_raw);
-+		context_free(con);
-+	}
-+#endif
-+	return 0;
-+      out:
-+	freecon(*sc);
-+	*sc = NULL;
-+	return -1;
-+}
-+
-+static void
-+ssh_selinux_get_role_level(char **role, const char **level,
-+    Authctxt *the_authctxt)
-+{
-+	*role = NULL;
-+	*level = NULL;
-+	if (the_authctxt) {
-+		if (the_authctxt->role != NULL) {
-+			char *slash;
-+			*role = xstrdup(the_authctxt->role);
-+			if ((slash = strchr(*role, '/')) != NULL) {
-+				*slash = '\0';
-+				*level = slash + 1;
-+			}
-+		}
-+	}
-+}
-+
-+/* Return the default security context for the given username */
-+static int
-+sshd_selinux_getctxbyname(char *pwname, security_context_t *default_sc,
-+    security_context_t *user_sc, int inetd, Authctxt *the_authctxt)
-+{
-+	char *sename, *lvl;
-+	char *role;
-+	const char *reqlvl;
-+	int r = 0;
-+	context_t con = NULL;
-+
-+	ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
-+
-+#ifdef HAVE_GETSEUSERBYNAME
-+	if ((r=getseuserbyname(pwname, &sename, &lvl)) != 0) {
-+		sename = NULL;
-+		lvl = NULL;
-+	}
-+#else
-+	sename = pwname;
-+	lvl = "";
-+#endif
-+
-+	if (r == 0) {
-+#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
-+		r = get_default_context_with_level(sename, lvl, NULL, default_sc);
-+#else
-+		r = get_default_context(sename, NULL, default_sc);
-+#endif
-+	}
-+
-+	if (r == 0) {
-+		/* If launched from xinetd, we must use current level */
-+		if (inetd) {
-+			security_context_t sshdsc=NULL;
-+
-+			if (getcon_raw(&sshdsc) < 0)
-+				fatal("failed to allocate security context");
-+
-+			if ((con=context_new(sshdsc)) == NULL)
-+				fatal("failed to allocate selinux context");
-+			reqlvl = context_range_get(con);
-+			freecon(sshdsc);
-+			if (reqlvl !=NULL && lvl != NULL && strcmp(reqlvl, lvl) == 0)
-+			    /* we actually don't change level */
-+			    reqlvl = "";
-+
-+			debug_f("current connection level '%s'", reqlvl);
-+
-+		}
-+
-+		if ((reqlvl != NULL && reqlvl[0]) || (role != NULL && role[0])) {
-+			r = get_user_context(sename, role, reqlvl, user_sc);
-+
-+			if (r == 0 && reqlvl != NULL && reqlvl[0]) {
-+				security_context_t default_level_sc = *default_sc;
-+				if (role != NULL && role[0]) {
-+					if (get_user_context(sename, role, lvl, &default_level_sc) < 0)
-+						default_level_sc = *default_sc;
-+				}
-+				/* verify that the requested range is contained in the user range */
-+				if (mls_range_allowed(default_level_sc, *user_sc)) {
-+					logit("permit MLS level %s (user range %s)", reqlvl, lvl);
-+				} else {
-+					r = -1;
-+					error("deny MLS level %s (user range %s)", reqlvl, lvl);
-+				}
-+				if (default_level_sc != *default_sc)
-+					freecon(default_level_sc);
-+			}
-+		} else {
-+			*user_sc = *default_sc;
-+		}
-+	}
-+	if (r != 0) {
-+		error_f("Failed to get default SELinux security "
-+		    "context for %s", pwname);
-+	}
-+
-+#ifdef HAVE_GETSEUSERBYNAME
-+	free(sename);
-+	free(lvl);
-+#endif
-+
-+	if (role != NULL)
-+		free(role);
-+	if (con)
-+		context_free(con);
-+
-+	return (r);
-+}
-+
-+/* Setup environment variables for pam_selinux */
-+static int
-+sshd_selinux_setup_variables(int(*set_it)(char *, const char *), int inetd,
-+    Authctxt *the_authctxt)
-+{
-+	const char *reqlvl;
-+	char *role;
-+	char *use_current;
-+	int rv;
-+
-+	debug3_f("setting execution context");
-+
-+	ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
-+
-+	rv = set_it("SELINUX_ROLE_REQUESTED", role ? role : "");
-+
-+	if (inetd) {
-+		use_current = "1";
-+	} else {
-+		use_current = "";
-+		rv = rv || set_it("SELINUX_LEVEL_REQUESTED", reqlvl ? reqlvl: "");
-+	}
-+
-+	rv = rv || set_it("SELINUX_USE_CURRENT_RANGE", use_current);
-+
-+	if (role != NULL)
-+		free(role);
-+
-+	return rv;
-+}
-+
-+static int
-+sshd_selinux_setup_pam_variables(int inetd,
-+    int(pam_setenv)(char *, const char *), Authctxt *the_authctxt)
-+{
-+	return sshd_selinux_setup_variables(pam_setenv, inetd, the_authctxt);
-+}
-+
-+static int
-+do_setenv(char *name, const char *value)
-+{
-+	return setenv(name, value, 1);
-+}
-+
-+int
-+sshd_selinux_setup_env_variables(int inetd, void *the_authctxt)
-+{
-+	Authctxt *authctxt = (Authctxt *) the_authctxt;
-+	return sshd_selinux_setup_variables(do_setenv, inetd, authctxt);
-+}
-+
-+/* Set the execution context to the default for the specified user */
-+void
-+sshd_selinux_setup_exec_context(char *pwname, int inetd,
-+    int(pam_setenv)(char *, const char *), void *the_authctxt, int use_pam)
-+{
-+	security_context_t user_ctx = NULL;
-+	int r = 0;
-+	security_context_t default_ctx = NULL;
-+	Authctxt *authctxt = (Authctxt *) the_authctxt;
-+
-+	if (!sshd_selinux_enabled())
-+		return;
-+
-+	if (use_pam) {
-+		/* do not compute context, just setup environment for pam_selinux */
-+		if (sshd_selinux_setup_pam_variables(inetd, pam_setenv, authctxt)) {
-+			switch (security_getenforce()) {
-+			case -1:
-+				fatal_f("security_getenforce() failed");
-+			case 0:
-+				error_f("SELinux PAM variable setup failure. Continuing in permissive mode.");
-+			break;
-+			default:
-+				fatal_f("SELinux PAM variable setup failure. Aborting connection.");
-+			}
-+		}
-+		return;
-+	}
-+
-+	debug3_f("setting execution context");
-+
-+	r = sshd_selinux_getctxbyname(pwname, &default_ctx, &user_ctx, inetd, authctxt);
-+	if (r >= 0) {
-+		r = setexeccon(user_ctx);
-+		if (r < 0) {
-+			error_f("Failed to set SELinux execution context %s for %s",
-+			    user_ctx, pwname);
-+		}
-+#ifdef HAVE_SETKEYCREATECON
-+		else if (setkeycreatecon(user_ctx) < 0) {
-+			error_f("Failed to set SELinux keyring creation context %s for %s",
-+			    user_ctx, pwname);
-+		}
-+#endif
-+	}
-+	if (user_ctx == NULL) {
-+		user_ctx = default_ctx;
-+	}
-+	if (r < 0 || user_ctx != default_ctx) {
-+		/* audit just the case when user changed a role or there was
-+		   a failure */
-+		sshd_selinux_send_audit_message(r >= 0, default_ctx, user_ctx);
-+	}
-+	if (r < 0) {
-+		switch (security_getenforce()) {
-+		case -1:
-+			fatal_f("security_getenforce() failed");
-+		case 0:
-+			error_f("ELinux failure. Continuing in permissive mode.");
-+			break;
-+		default:
-+			fatal_f("SELinux failure. Aborting connection.");
-+		}
-+	}
-+	if (user_ctx != NULL && user_ctx != default_ctx)
-+		freecon(user_ctx);
-+	if (default_ctx != NULL)
-+		freecon(default_ctx);
-+
-+	debug3_f("done");
-+}
-+
-+#endif
-+#endif
-+
-diff --git a/openbsd-compat/port-linux.c b/openbsd-compat/port-linux.c
-index c1d54f38d..7426f6f79 100644
---- a/openbsd-compat/port-linux.c
-+++ b/openbsd-compat/port-linux.c
-@@ -109,37 +109,6 @@ ssh_selinux_getctxbyname(char *pwname)
- 	return sc;
- }
- 
--/* Set the execution context to the default for the specified user */
--void
--ssh_selinux_setup_exec_context(char *pwname)
--{
--	char *user_ctx = NULL;
--
--	if (!ssh_selinux_enabled())
--		return;
--
--	debug3("%s: setting execution context", __func__);
--
--	user_ctx = ssh_selinux_getctxbyname(pwname);
--	if (setexeccon(user_ctx) != 0) {
--		switch (security_getenforce()) {
--		case -1:
--			fatal("%s: security_getenforce() failed", __func__);
--		case 0:
--			error("%s: Failed to set SELinux execution "
--			    "context for %s", __func__, pwname);
--			break;
--		default:
--			fatal("%s: Failed to set SELinux execution context "
--			    "for %s (in enforcing mode)", __func__, pwname);
--		}
--	}
--	if (user_ctx != NULL)
--		freecon(user_ctx);
--
--	debug3("%s: done", __func__);
--}
--
- /* Set the TTY context for the specified user */
- void
- ssh_selinux_setup_pty(char *pwname, const char *tty)
-@@ -152,7 +121,11 @@ ssh_selinux_setup_pty(char *pwname, const char *tty)
- 
- 	debug3("%s: setting TTY context on %s", __func__, tty);
- 
--	user_ctx = ssh_selinux_getctxbyname(pwname);
-+	if (getexeccon(&user_ctx) != 0) {
-+		error_f("getexeccon: %s", strerror(errno));
-+		goto out;
-+	}
-+
- 
- 	/* XXX: should these calls fatal() upon failure in enforcing mode? */
- 
-diff --git a/openbsd-compat/port-linux.h b/openbsd-compat/port-linux.h
-index 959430de1..26c5f773b 100644
---- a/openbsd-compat/port-linux.h
-+++ b/openbsd-compat/port-linux.h
-@@ -20,9 +20,12 @@
- #ifdef WITH_SELINUX
- int ssh_selinux_enabled(void);
- void ssh_selinux_setup_pty(char *, const char *);
--void ssh_selinux_setup_exec_context(char *);
- void ssh_selinux_change_context(const char *);
- void ssh_selinux_setfscreatecon(const char *);
-+
-+int sshd_selinux_enabled(void);
-+void sshd_selinux_setup_exec_context(char *, int, int(char *, const char *), void *, int);
-+int sshd_selinux_setup_env_variables(int inetd, void *);
- #endif
- 
- #ifdef LINUX_OOM_ADJUST
-diff --git a/platform.c b/platform.c
-index fd1a7a7c2..66d0c2a6b 100644
---- a/platform.c
-+++ b/platform.c
-@@ -33,6 +33,8 @@
- #include "openbsd-compat/openbsd-compat.h"
- 
- extern ServerOptions options;
-+extern int inetd_flag;
-+extern Authctxt *the_authctxt;
- 
- /* return 1 if we are running with privilege to swap UIDs, 0 otherwise */
- int
-@@ -55,7 +57,7 @@ platform_setusercontext(struct passwd *pw)
- {
- #ifdef WITH_SELINUX
- 	/* Cache selinux status for later use */
--	(void)ssh_selinux_enabled();
-+	(void)sshd_selinux_enabled();
- #endif
- 
- #ifdef USE_SOLARIS_PROJECTS
-@@ -140,7 +142,9 @@ platform_setusercontext_post_groups(struct passwd *pw)
- 	}
- #endif /* HAVE_SETPCRED */
- #ifdef WITH_SELINUX
--	ssh_selinux_setup_exec_context(pw->pw_name);
-+	sshd_selinux_setup_exec_context(pw->pw_name,
-+	    inetd_flag, do_pam_putenv, the_authctxt,
-+	    options.use_pam);
- #endif
- }
- 
-diff --git a/sshd-session.c b/sshd-session.c
-index e9a488d08..967737c5b 100644
---- a/sshd-session.c
-+++ b/sshd-session.c
-@@ -123,7 +123,7 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE;
- int debug_flag = 0;
- 
- /* Flag indicating that the daemon is being started from inetd. */
--static int inetd_flag = 0;
-+int inetd_flag = 0;
- 
- /* debug goes to stderr unless inetd_flag is set */
- static int log_stderr = 0;
-@@ -1274,6 +1274,11 @@ main(int ac, char **av)
- 		restore_uid();
- 	}
- #endif
-+#ifdef WITH_SELINUX
-+	sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
-+	    inetd_flag, do_pam_putenv, the_authctxt,
-+	    options.use_pam);
-+#endif
- #ifdef USE_PAM
- 	if (options.use_pam) {
- 		do_pam_setcred();
--- 
-2.53.0
-

diff --git a/0003-Implement-SELinux-environment-variable-setup-for-sub.patch b/0003-Implement-SELinux-environment-variable-setup-for-sub.patch
new file mode 100644
index 0000000..17abd68
--- /dev/null
+++ b/0003-Implement-SELinux-environment-variable-setup-for-sub.patch
@@ -0,0 +1,677 @@
+From f87a7cf01057bcf495974502de3e6f4bfc472a5d Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 13 Apr 2026 14:06:47 +0200
+Subject: [PATCH 03/54] Implement SELinux environment variable setup for
+ subprocess execution
+
+openssh-6.6p1-selinux-env-setup
+
+Implement comprehensive SELinux context management for sshd, including
+role selection, MLS level validation, and environment variable setup for
+pam_selinux integration.
+
+Changes:
+- openbsd-compat/port-linux-sshd.c: New file with SELinux context
+  management functions including sshd_selinux_getctxbyname(),
+  sshd_selinux_setup_exec_context(), and environment variable setup
+- openbsd-compat/Makefile.in: Add port-linux-sshd.o to build
+- openbsd-compat/port-linux.c: Remove old ssh_selinux_setup_exec_context(),
+  update ssh_selinux_setup_pty() to use getexeccon()
+- openbsd-compat/port-linux.h: Update function declarations for
+  sshd-specific SELinux functions
+- platform.c: Replace ssh_selinux_setup_exec_context() with
+  sshd_selinux_setup_exec_context() and pass additional parameters
+  (inetd_flag, do_pam_putenv, the_authctxt, options.use_pam)
+- sshd-session.c: Add SELinux execution context setup call in main()
+- auth-pam.c: Change do_pam_putenv() value parameter to const char *
+- auth-pam.h: Update do_pam_putenv() signature and add do_pam_chauthtok()
+  declaration
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ auth-pam.c                       |   2 +-
+ auth-pam.h                       |   3 +-
+ openbsd-compat/Makefile.in       |   3 +-
+ openbsd-compat/port-linux-sshd.c | 454 +++++++++++++++++++++++++++++++
+ openbsd-compat/port-linux.c      |  37 +--
+ openbsd-compat/port-linux.h      |   5 +-
+ platform.c                       |   8 +-
+ sshd-session.c                   |   7 +-
+ 8 files changed, 480 insertions(+), 39 deletions(-)
+ create mode 100644 openbsd-compat/port-linux-sshd.c
+
+diff --git a/auth-pam.c b/auth-pam.c
+index 29607e041..3f342f9e1 100644
+--- a/auth-pam.c
++++ b/auth-pam.c
+@@ -1145,7 +1145,7 @@ is_pam_session_open(void)
+  * during the ssh authentication process.
+  */
+ int
+-do_pam_putenv(char *name, char *value)
++do_pam_putenv(char *name, const char *value)
+ {
+ 	int ret = 1;
+ 	char *compound;
+diff --git a/auth-pam.h b/auth-pam.h
+index 491336701..f9167cfc8 100644
+--- a/auth-pam.h
++++ b/auth-pam.h
+@@ -32,7 +32,8 @@ void finish_pam(void);
+ u_int do_pam_account(void);
+ void do_pam_session(struct ssh *);
+ void do_pam_setcred(void);
+-int do_pam_putenv(char *, char *);
++void do_pam_chauthtok(void);
++int do_pam_putenv(char *, const char *);
+ char ** fetch_pam_environment(void);
+ char ** fetch_pam_child_environment(void);
+ void free_pam_environment(char **);
+diff --git a/openbsd-compat/Makefile.in b/openbsd-compat/Makefile.in
+index 53c87db6d..39531ae77 100644
+--- a/openbsd-compat/Makefile.in
++++ b/openbsd-compat/Makefile.in
+@@ -102,7 +102,8 @@ PORTS=	port-aix.o \
+ 	port-prngd.o \
+ 	port-solaris.o \
+ 	port-net.o \
+-	port-uw.o
++	port-uw.o \
++	port-linux-sshd.o
+ 
+ .c.o:
+ 	$(CC) $(CFLAGS_NOPIE) $(PICFLAG) $(CPPFLAGS) -c $<
+diff --git a/openbsd-compat/port-linux-sshd.c b/openbsd-compat/port-linux-sshd.c
+new file mode 100644
+index 000000000..ab083a637
+--- /dev/null
++++ b/openbsd-compat/port-linux-sshd.c
+@@ -0,0 +1,454 @@
++/*
++ * Copyright (c) 2005 Daniel Walsh <dwalsh@redhat.com>
++ * Copyright (c) 2014 Petr Lautrbach <plautrba@redhat.com>
++ *
++ * 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.
++ */
++
++/*
++ * Linux-specific portability code - just SELinux support for sshd at present
++ */
++
++#include "includes.h"
++
++#if defined(WITH_SELINUX) || defined(LINUX_OOM_ADJUST)
++#include <errno.h>
++#include <stdarg.h>
++#include <string.h>
++#include <stdio.h>
++#include <stdlib.h>
++
++#include "log.h"
++#include "xmalloc.h"
++#include "misc.h"      /* servconf.h needs misc.h for struct ForwardOptions */
++#include "servconf.h"
++#include "port-linux.h"
++#include "sshkey.h"
++#include "hostfile.h"
++#include "auth.h"
++
++#ifdef WITH_SELINUX
++#include <selinux/selinux.h>
++#include <selinux/context.h>
++#include <selinux/get_context_list.h>
++#include <selinux/get_default_type.h>
++
++#ifdef HAVE_LINUX_AUDIT
++#include <libaudit.h>
++#include <unistd.h>
++#endif
++
++/* Wrapper around is_selinux_enabled() to log its return value once only */
++int
++sshd_selinux_enabled(void)
++{
++	static int enabled = -1;
++
++	if (enabled == -1) {
++		enabled = (is_selinux_enabled() == 1);
++		debug("SELinux support %s", enabled ? "enabled" : "disabled");
++	}
++
++	return (enabled);
++}
++
++/* Send audit message */
++static int
++sshd_selinux_send_audit_message(int success, security_context_t default_context,
++		       security_context_t selected_context)
++{
++	int rc=0;
++#ifdef HAVE_LINUX_AUDIT
++	char *msg = NULL;
++	int audit_fd = audit_open();
++	security_context_t default_raw=NULL;
++	security_context_t selected_raw=NULL;
++	rc = -1;
++	if (audit_fd < 0) {
++		if (errno == EINVAL || errno == EPROTONOSUPPORT ||
++					errno == EAFNOSUPPORT)
++				return 0; /* No audit support in kernel */
++		error("Error connecting to audit system.");
++		return rc;
++	}
++	if (selinux_trans_to_raw_context(default_context, &default_raw) < 0) {
++		error("Error translating default context.");
++		default_raw = NULL;
++	}
++	if (selinux_trans_to_raw_context(selected_context, &selected_raw) < 0) {
++		error("Error translating selected context.");
++		selected_raw = NULL;
++	}
++	if (asprintf(&msg, "sshd: default-context=%s selected-context=%s",
++		     default_raw ? default_raw : (default_context ? default_context: "?"),
++		     selected_context ? selected_raw : (selected_context ? selected_context :"?")) < 0) {
++		error("Error allocating memory.");
++		goto out;
++	}
++	if (audit_log_user_message(audit_fd, AUDIT_USER_ROLE_CHANGE,
++				   msg, NULL, NULL, NULL, success) <= 0) {
++		error("Error sending audit message.");
++		goto out;
++	}
++	rc = 0;
++      out:
++	free(msg);
++	freecon(default_raw);
++	freecon(selected_raw);
++	close(audit_fd);
++#endif
++	return rc;
++}
++
++static int
++mls_range_allowed(security_context_t src, security_context_t dst)
++{
++	struct av_decision avd;
++	int retval;
++	access_vector_t bit;
++	security_class_t class;
++
++	debug_f("src:%s dst:%s", src, dst);
++	class = string_to_security_class("context");
++	if (!class) {
++		error("string_to_security_class failed to translate security class context");
++		return 1;
++	}
++	bit = string_to_av_perm(class, "contains");
++	if (!bit) {
++		error("string_to_av_perm failed to translate av perm contains");
++		return 1;
++	}
++	retval = security_compute_av(src, dst, class, bit, &avd);
++	if (retval || ((bit & avd.allowed) != bit))
++		return 0;
++
++	return 1;
++}
++
++static int
++get_user_context(const char *sename, const char *role, const char *lvl,
++	security_context_t *sc) {
++#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
++	if (lvl == NULL || lvl[0] == '\0' || get_default_context_with_level(sename, lvl, NULL, sc) != 0) {
++	        /* User may have requested a level completely outside of his 
++	           allowed range. We get a context just for auditing as the
++	           range check below will certainly fail for default context. */
++#endif
++		if (get_default_context(sename, NULL, sc) != 0) {
++			*sc = NULL;
++			return -1;
++		}
++#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
++	}
++#endif
++	if (role != NULL && role[0]) {
++		context_t con;
++		char *type=NULL;
++		if (get_default_type(role, &type) != 0) {
++			error("get_default_type: failed to get default type for '%s'",
++				role);
++			goto out;
++		}
++		con = context_new(*sc);
++		if (!con) {
++			goto out;
++		}
++		context_role_set(con, role);
++		context_type_set(con, type);
++		freecon(*sc);
++		*sc = strdup(context_str(con));
++		context_free(con);
++		if (!*sc)
++			return -1;
++	}
++#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
++	if (lvl != NULL && lvl[0]) {
++		/* verify that the requested range is obtained */
++		context_t con;
++		security_context_t obtained_raw;
++		security_context_t requested_raw;
++		con = context_new(*sc);
++		if (!con) {
++			goto out;
++		}
++		context_range_set(con, lvl);
++		if (selinux_trans_to_raw_context(*sc, &obtained_raw) < 0) {
++			context_free(con);
++			goto out;
++		}
++		if (selinux_trans_to_raw_context(context_str(con), &requested_raw) < 0) {
++			freecon(obtained_raw);
++			context_free(con);
++			goto out;
++		}
++
++		debug("get_user_context: obtained context '%s' requested context '%s'",
++			obtained_raw, requested_raw);
++		if (strcmp(obtained_raw, requested_raw)) {
++			/* set the context to the real requested one but fail */
++			freecon(requested_raw);
++			freecon(obtained_raw);
++			freecon(*sc);
++			*sc = strdup(context_str(con));
++			context_free(con);
++			return -1;
++		}
++		freecon(requested_raw);
++		freecon(obtained_raw);
++		context_free(con);
++	}
++#endif
++	return 0;
++      out:
++	freecon(*sc);
++	*sc = NULL;
++	return -1;
++}
++
++static void
++ssh_selinux_get_role_level(char **role, const char **level,
++    Authctxt *the_authctxt)
++{
++	*role = NULL;
++	*level = NULL;
++	if (the_authctxt) {
++		if (the_authctxt->role != NULL) {
++			char *slash;
++			*role = xstrdup(the_authctxt->role);
++			if ((slash = strchr(*role, '/')) != NULL) {
++				*slash = '\0';
++				*level = slash + 1;
++			}
++		}
++	}
++}
++
++/* Return the default security context for the given username */
++static int
++sshd_selinux_getctxbyname(char *pwname, security_context_t *default_sc,
++    security_context_t *user_sc, int inetd, Authctxt *the_authctxt)
++{
++	char *sename, *lvl;
++	char *role;
++	const char *reqlvl;
++	int r = 0;
++	context_t con = NULL;
++
++	ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
++
++#ifdef HAVE_GETSEUSERBYNAME
++	if ((r=getseuserbyname(pwname, &sename, &lvl)) != 0) {
++		sename = NULL;
++		lvl = NULL;
++	}
++#else
++	sename = pwname;
++	lvl = "";
++#endif
++
++	if (r == 0) {
++#ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
++		r = get_default_context_with_level(sename, lvl, NULL, default_sc);
++#else
++		r = get_default_context(sename, NULL, default_sc);
++#endif
++	}
++
++	if (r == 0) {
++		/* If launched from xinetd, we must use current level */
++		if (inetd) {
++			security_context_t sshdsc=NULL;
++
++			if (getcon_raw(&sshdsc) < 0)
++				fatal("failed to allocate security context");
++
++			if ((con=context_new(sshdsc)) == NULL)
++				fatal("failed to allocate selinux context");
++			reqlvl = context_range_get(con);
++			freecon(sshdsc);
++			if (reqlvl !=NULL && lvl != NULL && strcmp(reqlvl, lvl) == 0)
++			    /* we actually don't change level */
++			    reqlvl = "";
++
++			debug_f("current connection level '%s'", reqlvl);
++
++		}
++
++		if ((reqlvl != NULL && reqlvl[0]) || (role != NULL && role[0])) {
++			r = get_user_context(sename, role, reqlvl, user_sc);
++
++			if (r == 0 && reqlvl != NULL && reqlvl[0]) {
++				security_context_t default_level_sc = *default_sc;
++				if (role != NULL && role[0]) {
++					if (get_user_context(sename, role, lvl, &default_level_sc) < 0)
++						default_level_sc = *default_sc;
++				}
++				/* verify that the requested range is contained in the user range */
++				if (mls_range_allowed(default_level_sc, *user_sc)) {
++					logit("permit MLS level %s (user range %s)", reqlvl, lvl);
++				} else {
++					r = -1;
++					error("deny MLS level %s (user range %s)", reqlvl, lvl);
++				}
++				if (default_level_sc != *default_sc)
++					freecon(default_level_sc);
++			}
++		} else {
++			*user_sc = *default_sc;
++		}
++	}
++	if (r != 0) {
++		error_f("Failed to get default SELinux security "
++		    "context for %s", pwname);
++	}
++
++#ifdef HAVE_GETSEUSERBYNAME
++	free(sename);
++	free(lvl);
++#endif
++
++	if (role != NULL)
++		free(role);
++	if (con)
++		context_free(con);
++
++	return (r);
++}
++
++/* Setup environment variables for pam_selinux */
++static int
++sshd_selinux_setup_variables(int(*set_it)(char *, const char *), int inetd,
++    Authctxt *the_authctxt)
++{
++	const char *reqlvl;
++	char *role;
++	char *use_current;
++	int rv;
++
++	debug3_f("setting execution context");
++
++	ssh_selinux_get_role_level(&role, &reqlvl, the_authctxt);
++
++	rv = set_it("SELINUX_ROLE_REQUESTED", role ? role : "");
++
++	if (inetd) {
++		use_current = "1";
++	} else {
++		use_current = "";
++		rv = rv || set_it("SELINUX_LEVEL_REQUESTED", reqlvl ? reqlvl: "");
++	}
++
++	rv = rv || set_it("SELINUX_USE_CURRENT_RANGE", use_current);
++
++	if (role != NULL)
++		free(role);
++
++	return rv;
++}
++
++static int
++sshd_selinux_setup_pam_variables(int inetd,
++    int(pam_setenv)(char *, const char *), Authctxt *the_authctxt)
++{
++	return sshd_selinux_setup_variables(pam_setenv, inetd, the_authctxt);
++}
++
++static int
++do_setenv(char *name, const char *value)
++{
++	return setenv(name, value, 1);
++}
++
++int
++sshd_selinux_setup_env_variables(int inetd, void *the_authctxt)
++{
++	Authctxt *authctxt = (Authctxt *) the_authctxt;
++	return sshd_selinux_setup_variables(do_setenv, inetd, authctxt);
++}
++
++/* Set the execution context to the default for the specified user */
++void
++sshd_selinux_setup_exec_context(char *pwname, int inetd,
++    int(pam_setenv)(char *, const char *), void *the_authctxt, int use_pam)
++{
++	security_context_t user_ctx = NULL;
++	int r = 0;
++	security_context_t default_ctx = NULL;
++	Authctxt *authctxt = (Authctxt *) the_authctxt;
++
++	if (!sshd_selinux_enabled())
++		return;
++
++	if (use_pam) {
++		/* do not compute context, just setup environment for pam_selinux */
++		if (sshd_selinux_setup_pam_variables(inetd, pam_setenv, authctxt)) {
++			switch (security_getenforce()) {
++			case -1:
++				fatal_f("security_getenforce() failed");
++			case 0:
++				error_f("SELinux PAM variable setup failure. Continuing in permissive mode.");
++			break;
++			default:
++				fatal_f("SELinux PAM variable setup failure. Aborting connection.");
++			}
++		}
++		return;
++	}
++
++	debug3_f("setting execution context");
++
++	r = sshd_selinux_getctxbyname(pwname, &default_ctx, &user_ctx, inetd, authctxt);
++	if (r >= 0) {
++		r = setexeccon(user_ctx);
++		if (r < 0) {
++			error_f("Failed to set SELinux execution context %s for %s",
++			    user_ctx, pwname);
++		}
++#ifdef HAVE_SETKEYCREATECON
++		else if (setkeycreatecon(user_ctx) < 0) {
++			error_f("Failed to set SELinux keyring creation context %s for %s",
++			    user_ctx, pwname);
++		}
++#endif
++	}
++	if (user_ctx == NULL) {
++		user_ctx = default_ctx;
++	}
++	if (r < 0 || user_ctx != default_ctx) {
++		/* audit just the case when user changed a role or there was
++		   a failure */
++		sshd_selinux_send_audit_message(r >= 0, default_ctx, user_ctx);
++	}
++	if (r < 0) {
++		switch (security_getenforce()) {
++		case -1:
++			fatal_f("security_getenforce() failed");
++		case 0:
++			error_f("ELinux failure. Continuing in permissive mode.");
++			break;
++		default:
++			fatal_f("SELinux failure. Aborting connection.");
++		}
++	}
++	if (user_ctx != NULL && user_ctx != default_ctx)
++		freecon(user_ctx);
++	if (default_ctx != NULL)
++		freecon(default_ctx);
++
++	debug3_f("done");
++}
++
++#endif
++#endif
++
+diff --git a/openbsd-compat/port-linux.c b/openbsd-compat/port-linux.c
+index c1d54f38d..7426f6f79 100644
+--- a/openbsd-compat/port-linux.c
++++ b/openbsd-compat/port-linux.c
+@@ -109,37 +109,6 @@ ssh_selinux_getctxbyname(char *pwname)
+ 	return sc;
+ }
+ 
+-/* Set the execution context to the default for the specified user */
+-void
+-ssh_selinux_setup_exec_context(char *pwname)
+-{
+-	char *user_ctx = NULL;
+-
+-	if (!ssh_selinux_enabled())
+-		return;
+-
+-	debug3("%s: setting execution context", __func__);
+-
+-	user_ctx = ssh_selinux_getctxbyname(pwname);
+-	if (setexeccon(user_ctx) != 0) {
+-		switch (security_getenforce()) {
+-		case -1:
+-			fatal("%s: security_getenforce() failed", __func__);
+-		case 0:
+-			error("%s: Failed to set SELinux execution "
+-			    "context for %s", __func__, pwname);
+-			break;
+-		default:
+-			fatal("%s: Failed to set SELinux execution context "
+-			    "for %s (in enforcing mode)", __func__, pwname);
+-		}
+-	}
+-	if (user_ctx != NULL)
+-		freecon(user_ctx);
+-
+-	debug3("%s: done", __func__);
+-}
+-
+ /* Set the TTY context for the specified user */
+ void
+ ssh_selinux_setup_pty(char *pwname, const char *tty)
+@@ -152,7 +121,11 @@ ssh_selinux_setup_pty(char *pwname, const char *tty)
+ 
+ 	debug3("%s: setting TTY context on %s", __func__, tty);
+ 
+-	user_ctx = ssh_selinux_getctxbyname(pwname);
++	if (getexeccon(&user_ctx) != 0) {
++		error_f("getexeccon: %s", strerror(errno));
++		goto out;
++	}
++
+ 
+ 	/* XXX: should these calls fatal() upon failure in enforcing mode? */
+ 
+diff --git a/openbsd-compat/port-linux.h b/openbsd-compat/port-linux.h
+index 959430de1..26c5f773b 100644
+--- a/openbsd-compat/port-linux.h
++++ b/openbsd-compat/port-linux.h
+@@ -20,9 +20,12 @@
+ #ifdef WITH_SELINUX
+ int ssh_selinux_enabled(void);
+ void ssh_selinux_setup_pty(char *, const char *);
+-void ssh_selinux_setup_exec_context(char *);
+ void ssh_selinux_change_context(const char *);
+ void ssh_selinux_setfscreatecon(const char *);
++
++int sshd_selinux_enabled(void);
++void sshd_selinux_setup_exec_context(char *, int, int(char *, const char *), void *, int);
++int sshd_selinux_setup_env_variables(int inetd, void *);
+ #endif
+ 
+ #ifdef LINUX_OOM_ADJUST
+diff --git a/platform.c b/platform.c
+index fd1a7a7c2..66d0c2a6b 100644
+--- a/platform.c
++++ b/platform.c
+@@ -33,6 +33,8 @@
+ #include "openbsd-compat/openbsd-compat.h"
+ 
+ extern ServerOptions options;
++extern int inetd_flag;
++extern Authctxt *the_authctxt;
+ 
+ /* return 1 if we are running with privilege to swap UIDs, 0 otherwise */
+ int
+@@ -55,7 +57,7 @@ platform_setusercontext(struct passwd *pw)
+ {
+ #ifdef WITH_SELINUX
+ 	/* Cache selinux status for later use */
+-	(void)ssh_selinux_enabled();
++	(void)sshd_selinux_enabled();
+ #endif
+ 
+ #ifdef USE_SOLARIS_PROJECTS
+@@ -140,7 +142,9 @@ platform_setusercontext_post_groups(struct passwd *pw)
+ 	}
+ #endif /* HAVE_SETPCRED */
+ #ifdef WITH_SELINUX
+-	ssh_selinux_setup_exec_context(pw->pw_name);
++	sshd_selinux_setup_exec_context(pw->pw_name,
++	    inetd_flag, do_pam_putenv, the_authctxt,
++	    options.use_pam);
+ #endif
+ }
+ 
+diff --git a/sshd-session.c b/sshd-session.c
+index be30c8d70..d7d4f32c5 100644
+--- a/sshd-session.c
++++ b/sshd-session.c
+@@ -123,7 +123,7 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE;
+ int debug_flag = 0;
+ 
+ /* Flag indicating that the daemon is being started from inetd. */
+-static int inetd_flag = 0;
++int inetd_flag = 0;
+ 
+ /* debug goes to stderr unless inetd_flag is set */
+ static int log_stderr = 0;
+@@ -1275,6 +1275,11 @@ main(int ac, char **av)
+ 		restore_uid();
+ 	}
+ #endif
++#ifdef WITH_SELINUX
++	sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
++	    inetd_flag, do_pam_putenv, the_authctxt,
++	    options.use_pam);
++#endif
+ #ifdef USE_PAM
+ 	if (options.use_pam) {
+ 		do_pam_setcred();
+-- 
+2.55.0
+

diff --git a/0003-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch b/0003-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
deleted file mode 100644
index 779a6ef..0000000
--- a/0003-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
+++ /dev/null
@@ -1,74 +0,0 @@
-From 8423e892d7532589c2df0598470e01eaa383fb6b Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 13 Apr 2026 14:07:02 +0200
-Subject: [PATCH 03/54] Pass inetd flags and auth context to subprocess calls
- for SELinux
-
-openssh-7.6p1-subprocess-selinux
-
-Pass inetd flags and authentication context down to subprocess calls
-for SELinux environment setup. This ensures that AuthorizedKeysCommand
-and AuthorizedPrincipalsCommand subprocesses run with the correct
-SELinux context based on the authenticated user's role and level.
-
-Changes:
-- misc.c: Update subprocess() function signature to accept inetd and
-  the_authctxt parameters; add sshd_selinux_setup_env_variables() call
-  in child process before execve
-- misc.h: Update subprocess() function declaration
-- auth2-pubkey.c: Add extern declarations for inetd_flag and
-  the_authctxt; pass them to subprocess() calls for
-  AuthorizedKeysCommand and AuthorizedPrincipalsCommand
-- sshconnect.c: Update subprocess() call with new parameters (0, NULL)
-- sshd-auth.c: Make inetd_flag non-static (extern)
-- sshd-session.c: Make inetd_flag non-static (extern)
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- misc.h       | 2 +-
- sshconnect.c | 2 +-
- sshd-auth.c  | 2 +-
- 3 files changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/misc.h b/misc.h
-index 791876c1e..fe11f9d8a 100644
---- a/misc.h
-+++ b/misc.h
-@@ -129,7 +129,7 @@ typedef void privrestore_fn(void);
- #define	SSH_SUBPROCESS_UNSAFE_PATH	(1<<3)	/* Don't check for safe cmd */
- #define	SSH_SUBPROCESS_PRESERVE_ENV	(1<<4)	/* Keep parent environment */
- pid_t subprocess(const char *, const char *, int, char **, FILE **, u_int,
--    struct passwd *, privdrop_fn *, privrestore_fn *);
-+    struct passwd *, privdrop_fn *, privrestore_fn *, int, void *);
- 
- typedef struct arglist arglist;
- struct arglist {
-diff --git a/sshconnect.c b/sshconnect.c
-index 4384277a6..5e761561d 100644
---- a/sshconnect.c
-+++ b/sshconnect.c
-@@ -911,7 +911,7 @@ load_hostkeys_command(struct hostkeys *hostkeys, const char *command_template,
- 
- 	if ((pid = subprocess(tag, command, ac, av, &f,
- 	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_UNSAFE_PATH|
--	    SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL)) == 0)
-+	    SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL, 0, NULL)) == 0)
- 		goto out;
- 
- 	load_hostkeys_file(hostkeys, hostfile_hostname, tag, f, 1);
-diff --git a/sshd-auth.c b/sshd-auth.c
-index 76350a2a3..73acb0dbe 100644
---- a/sshd-auth.c
-+++ b/sshd-auth.c
-@@ -119,7 +119,7 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE;
- int debug_flag = 0;
- 
- /* Flag indicating that the daemon is being started from inetd. */
--static int inetd_flag = 0;
-+int inetd_flag = 0;
- 
- /* Saved arguments to main(). */
- static char **saved_argv;
--- 
-2.53.0
-

diff --git a/0004-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch b/0004-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
new file mode 100644
index 0000000..8a6555d
--- /dev/null
+++ b/0004-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
@@ -0,0 +1,74 @@
+From df67685c029a001ce4eed58aebd522c1dbf54cd5 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 13 Apr 2026 14:07:02 +0200
+Subject: [PATCH 04/54] Pass inetd flags and auth context to subprocess calls
+ for SELinux
+
+openssh-7.6p1-subprocess-selinux
+
+Pass inetd flags and authentication context down to subprocess calls
+for SELinux environment setup. This ensures that AuthorizedKeysCommand
+and AuthorizedPrincipalsCommand subprocesses run with the correct
+SELinux context based on the authenticated user's role and level.
+
+Changes:
+- misc.c: Update subprocess() function signature to accept inetd and
+  the_authctxt parameters; add sshd_selinux_setup_env_variables() call
+  in child process before execve
+- misc.h: Update subprocess() function declaration
+- auth2-pubkey.c: Add extern declarations for inetd_flag and
+  the_authctxt; pass them to subprocess() calls for
+  AuthorizedKeysCommand and AuthorizedPrincipalsCommand
+- sshconnect.c: Update subprocess() call with new parameters (0, NULL)
+- sshd-auth.c: Make inetd_flag non-static (extern)
+- sshd-session.c: Make inetd_flag non-static (extern)
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ misc.h       | 2 +-
+ sshconnect.c | 2 +-
+ sshd-auth.c  | 2 +-
+ 3 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/misc.h b/misc.h
+index 791876c1e..fe11f9d8a 100644
+--- a/misc.h
++++ b/misc.h
+@@ -129,7 +129,7 @@ typedef void privrestore_fn(void);
+ #define	SSH_SUBPROCESS_UNSAFE_PATH	(1<<3)	/* Don't check for safe cmd */
+ #define	SSH_SUBPROCESS_PRESERVE_ENV	(1<<4)	/* Keep parent environment */
+ pid_t subprocess(const char *, const char *, int, char **, FILE **, u_int,
+-    struct passwd *, privdrop_fn *, privrestore_fn *);
++    struct passwd *, privdrop_fn *, privrestore_fn *, int, void *);
+ 
+ typedef struct arglist arglist;
+ struct arglist {
+diff --git a/sshconnect.c b/sshconnect.c
+index 0ddfc76b3..bdaade682 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -954,7 +954,7 @@ load_hostkeys_command(struct hostkeys *hostkeys, const char *command_template,
+ 
+ 	if ((pid = subprocess(tag, command, ac, av, &f,
+ 	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_UNSAFE_PATH|
+-	    SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL)) == 0)
++	    SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL, 0, NULL)) == 0)
+ 		goto out;
+ 
+ 	load_hostkeys_file(hostkeys, hostfile_hostname, tag, f, 1);
+diff --git a/sshd-auth.c b/sshd-auth.c
+index fb1bab66f..99359c718 100644
+--- a/sshd-auth.c
++++ b/sshd-auth.c
+@@ -119,7 +119,7 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE;
+ int debug_flag = 0;
+ 
+ /* Flag indicating that the daemon is being started from inetd. */
+-static int inetd_flag = 0;
++int inetd_flag = 0;
+ 
+ /* Saved arguments to main(). */
+ static char **saved_argv;
+-- 
+2.55.0
+

diff --git a/0004-openssh-6.6p1-keycat.patch b/0004-openssh-6.6p1-keycat.patch
deleted file mode 100644
index 329085c..0000000
--- a/0004-openssh-6.6p1-keycat.patch
+++ /dev/null
@@ -1,380 +0,0 @@
-From 5cc1b10c8cd5de91ec819a9397abd24aef9f099d Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 04/54] openssh-6.6p1-keycat
-
----
- .gitignore       |   1 +
- HOWTO.ssh-keycat |  12 +++
- Makefile.in      |   8 +-
- configure.ac     |   6 ++
- ssh-keycat.c     | 241 +++++++++++++++++++++++++++++++++++++++++++++++
- 5 files changed, 267 insertions(+), 1 deletion(-)
- create mode 100644 HOWTO.ssh-keycat
- create mode 100644 ssh-keycat.c
-
-diff --git a/.gitignore b/.gitignore
-index 907533067..7b4668d71 100644
---- a/.gitignore
-+++ b/.gitignore
-@@ -28,6 +28,7 @@ ssh-agent
- ssh-keygen
- ssh-keyscan
- ssh-keysign
-+ssh-keycat
- ssh-pkcs11-helper
- ssh-sk-helper
- sshd
-diff --git a/HOWTO.ssh-keycat b/HOWTO.ssh-keycat
-new file mode 100644
-index 000000000..630ec628c
---- /dev/null
-+++ b/HOWTO.ssh-keycat
-@@ -0,0 +1,12 @@
-+The ssh-keycat retrieves the content of the ~/.ssh/authorized_keys
-+of an user in any environment. This includes environments with
-+polyinstantiation of home directories and SELinux MLS policy enabled.
-+
-+To use ssh-keycat, set these options in /etc/ssh/sshd_config file:
-+        AuthorizedKeysCommand /usr/libexec/openssh/ssh-keycat
-+        AuthorizedKeysCommandUser root
-+
-+Do not forget to enable public key authentication:
-+        PubkeyAuthentication yes
-+
-+
-diff --git a/Makefile.in b/Makefile.in
-index 2aac879c1..42b45d33d 100644
---- a/Makefile.in
-+++ b/Makefile.in
-@@ -23,6 +23,7 @@ SSH_PROGRAM=@bindir@/ssh
- ASKPASS_PROGRAM=$(libexecdir)/ssh-askpass
- SFTP_SERVER=$(libexecdir)/sftp-server
- SSH_KEYSIGN=$(libexecdir)/ssh-keysign
-+SSH_KEYCAT=$(libexecdir)/ssh-keycat
- SSHD_SESSION=$(libexecdir)/sshd-session
- SSHD_AUTH=$(libexecdir)/sshd-auth
- SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
-@@ -58,6 +59,7 @@ CHANNELLIBS=@CHANNELLIBS@
- K5LIBS=@K5LIBS@
- GSSLIBS=@GSSLIBS@
- SSHDLIBS=@SSHDLIBS@
-+KEYCATLIBS=@KEYCATLIBS@
- LIBEDIT=@LIBEDIT@
- LIBFIDO2=@LIBFIDO2@
- LIBWTMPDB=@LIBWTMPDB@
-@@ -75,7 +77,7 @@ MKDIR_P=@MKDIR_P@
- 
- .SUFFIXES: .lo
- 
--TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) sshd-session$(EXEEXT) sshd-auth$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-sk-helper$(EXEEXT) $(SK_STANDALONE)
-+TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) sshd-session$(EXEEXT) sshd-auth$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-sk-helper$(EXEEXT) ssh-keycat$(EXEEXT) $(SK_STANDALONE)
- 
- LIBOPENSSH_OBJS=\
- 	ssh_api.o \
-@@ -252,6 +254,9 @@ ssh-pkcs11-helper$(EXEEXT): $(LIBCOMPAT) libssh.a $(P11HELPER_OBJS)
- ssh-sk-helper$(EXEEXT): $(LIBCOMPAT) libssh.a $(SKHELPER_OBJS)
- 	$(LD) -o $@ $(SKHELPER_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS) $(LIBFIDO2) $(CHANNELLIBS)
- 
-+ssh-keycat$(EXEEXT): $(LIBCOMPAT) $(SSHDOBJS) libssh.a ssh-keycat.o uidswap.o
-+	$(LD) -o $@ ssh-keycat.o uidswap.o $(LDFLAGS) -lssh -lopenbsd-compat $(KEYCATLIBS) $(CHANNELLIBS) $(LIBS)
-+
- ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHKEYSCAN_OBJS)
- 	$(LD) -o $@ $(SSHKEYSCAN_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS) $(CHANNELLIBS)
- 
-@@ -440,6 +445,7 @@ install-files:
- 	$(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign$(EXEEXT) $(DESTDIR)$(SSH_KEYSIGN)$(EXEEXT)
- 	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
- 	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-sk-helper$(EXEEXT) $(DESTDIR)$(SSH_SK_HELPER)$(EXEEXT)
-+	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-keycat$(EXEEXT) $(DESTDIR)$(libexecdir)/ssh-keycat$(EXEEXT)
- 	$(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
- 	$(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
- 	$(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
-diff --git a/configure.ac b/configure.ac
-index a8e9df66b..8f8276c78 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -3709,6 +3709,7 @@ AC_ARG_WITH([pam],
- 			PAM_MSG="yes"
- 
- 			SSHDLIBS="$SSHDLIBS -lpam"
-+			KEYCATLIBS="$KEYCATLIBS -lpam"
- 			AC_DEFINE([USE_PAM], [1],
- 				[Define if you want to enable PAM support])
- 
-@@ -3719,6 +3720,7 @@ AC_ARG_WITH([pam],
- 					;;
- 				*)
- 					SSHDLIBS="$SSHDLIBS -ldl"
-+					KEYCATLIBS="$KEYCATLIBS -ldl"
- 					;;
- 				esac
- 			fi
-@@ -4962,6 +4964,7 @@ AC_ARG_WITH([selinux],
- 	fi ]
- )
- AC_SUBST([SSHDLIBS])
-+AC_SUBST([KEYCATLIBS])
- 
- # Check whether user wants Kerberos 5 support
- KRB5_MSG="no"
-@@ -5975,6 +5978,9 @@ fi
- if test ! -z "${SSHDLIBS}"; then
- echo "         +for sshd: ${SSHDLIBS}"
- fi
-+if test ! -z "${KEYCATLIBS}"; then
-+echo "   +for ssh-keycat: ${KEYCATLIBS}"
-+fi
- 
- echo ""
- 
-diff --git a/ssh-keycat.c b/ssh-keycat.c
-new file mode 100644
-index 000000000..8bb17c73e
---- /dev/null
-+++ b/ssh-keycat.c
-@@ -0,0 +1,241 @@
-+/*
-+ * 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, and the entire permission notice in its entirety,
-+ *    including the disclaimer of warranties.
-+ * 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.
-+ * 3. The name of the author may not be used to endorse or promote
-+ *    products derived from this software without specific prior
-+ *    written permission.
-+ *
-+ * ALTERNATIVELY, this product may be distributed under the terms of
-+ * the GNU Public License, in which case the provisions of the GPL are
-+ * required INSTEAD OF the above restrictions.  (This clause is
-+ * necessary due to a potential bad interaction between the GPL and
-+ * the restrictions contained in a BSD-style copyright.)
-+ *
-+ * THIS SOFTWARE IS PROVIDED ``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.
-+ */
-+
-+/*
-+ * Copyright (c) 2011 Red Hat, Inc.
-+ * Written by Tomas Mraz <tmraz@redhat.com>
-+*/
-+
-+#define _GNU_SOURCE
-+
-+#include "config.h"
-+#include <stdio.h>
-+#include <stdlib.h>
-+#include <string.h>
-+#include <sys/types.h>
-+#include <sys/stat.h>
-+#include <pwd.h>
-+#include <fcntl.h>
-+#include <unistd.h>
-+#ifdef HAVE_STDINT_H
-+#include <stdint.h>
-+#endif
-+
-+#include <security/pam_appl.h>
-+
-+#include "uidswap.h"
-+#include "misc.h"
-+
-+#define ERR_USAGE 1
-+#define ERR_PAM_START 2
-+#define ERR_OPEN_SESSION 3
-+#define ERR_CLOSE_SESSION 4
-+#define ERR_PAM_END 5
-+#define ERR_GETPWNAM 6
-+#define ERR_MEMORY 7
-+#define ERR_OPEN 8
-+#define ERR_FILE_MODE 9
-+#define ERR_FDOPEN 10
-+#define ERR_STAT 11
-+#define ERR_WRITE 12
-+#define ERR_PAM_PUTENV 13
-+#define BUFLEN 4096
-+
-+/* Just ignore the messages in the conversation function */
-+static int
-+dummy_conv(int num_msg, const struct pam_message **msgm,
-+	   struct pam_response **response, void *appdata_ptr)
-+{
-+	struct pam_response *rsp;
-+
-+	(void)msgm;
-+	(void)appdata_ptr;
-+
-+	if (num_msg <= 0)
-+		return PAM_CONV_ERR;
-+
-+	/* Just allocate the array as empty responses */
-+	rsp = calloc (num_msg, sizeof (struct pam_response));
-+	if (rsp == NULL)
-+		return PAM_CONV_ERR;
-+
-+	*response = rsp;
-+	return PAM_SUCCESS;
-+}
-+
-+static struct pam_conv conv = {
-+	dummy_conv,
-+	NULL
-+};
-+
-+char *
-+make_auth_keys_name(const struct passwd *pwd)
-+{
-+	char *fname;
-+
-+	if (asprintf(&fname, "%s/.ssh/authorized_keys", pwd->pw_dir) < 0)
-+		return NULL;
-+
-+	return fname;
-+}
-+
-+int
-+dump_keys(const char *user)
-+{
-+	struct passwd *pwd;
-+	int fd = -1;
-+	FILE *f = NULL;
-+	char *fname = NULL;
-+	int rv = 0;
-+	char buf[BUFLEN];
-+	size_t len;
-+	struct stat st;
-+
-+	if ((pwd = getpwnam(user)) == NULL) {
-+		return ERR_GETPWNAM;
-+	}
-+
-+	if ((fname = make_auth_keys_name(pwd)) == NULL) {
-+		return ERR_MEMORY;
-+	}
-+
-+	temporarily_use_uid(pwd);
-+
-+	if ((fd = open(fname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < 0) {
-+		rv = ERR_OPEN;
-+		goto fail;
-+	}
-+
-+	if (fstat(fd, &st) < 0) {
-+		rv = ERR_STAT;
-+		goto fail;
-+	}
-+
-+	if (!S_ISREG(st.st_mode) ||
-+		(st.st_uid != pwd->pw_uid && st.st_uid != 0)) {
-+		rv = ERR_FILE_MODE;
-+		goto fail;
-+	}
-+
-+	unset_nonblock(fd);
-+
-+	if ((f = fdopen(fd, "r")) == NULL) {
-+		rv = ERR_FDOPEN;
-+		goto fail;
-+	}
-+
-+	fd = -1;
-+
-+	while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
-+		rv = fwrite(buf, 1, len, stdout) != len ? ERR_WRITE : 0;
-+	}
-+
-+fail:
-+	if (fd != -1)
-+		close(fd);
-+	if (f != NULL)
-+		fclose(f);
-+	free(fname);
-+	restore_uid();
-+	return rv;
-+}
-+
-+static const char *env_names[] = { "SELINUX_ROLE_REQUESTED",
-+	"SELINUX_LEVEL_REQUESTED",
-+	"SELINUX_USE_CURRENT_RANGE"
-+};
-+
-+extern char **environ;
-+
-+int
-+set_pam_environment(pam_handle_t *pamh)
-+{
-+	int i;
-+	size_t j;
-+
-+	for (j = 0; j < sizeof(env_names)/sizeof(env_names[0]); ++j) {
-+		int len = strlen(env_names[j]);
-+
-+		for (i = 0; environ[i] != NULL; ++i) {
-+			if (strncmp(env_names[j], environ[i], len) == 0 &&
-+			    environ[i][len] == '=') {
-+				if (pam_putenv(pamh, environ[i]) != PAM_SUCCESS)
-+					return ERR_PAM_PUTENV;
-+			}
-+		}
-+	}
-+
-+	return 0;
-+}
-+
-+int
-+main(int argc, char *argv[])
-+{
-+	pam_handle_t *pamh = NULL;
-+	int retval;
-+	int ev = 0;
-+
-+	if (argc != 2) {
-+		fprintf(stderr, "Usage: %s <user-name>\n", argv[0]);
-+		return ERR_USAGE;
-+	}
-+
-+	retval = pam_start("ssh-keycat", argv[1], &conv, &pamh);
-+	if (retval != PAM_SUCCESS) {
-+		return ERR_PAM_START;
-+	}
-+
-+	ev = set_pam_environment(pamh);
-+	if (ev != 0)
-+		goto finish;
-+
-+	retval = pam_open_session(pamh, PAM_SILENT);
-+	if (retval != PAM_SUCCESS) {
-+		ev = ERR_OPEN_SESSION;
-+		goto finish;
-+	}
-+
-+	ev = dump_keys(argv[1]);
-+
-+	retval = pam_close_session(pamh, PAM_SILENT);
-+	if (retval != PAM_SUCCESS) {
-+		ev = ERR_CLOSE_SESSION;
-+	}
-+
-+finish:
-+	retval = pam_end (pamh,retval);
-+	if (retval != PAM_SUCCESS) {
-+		ev = ERR_PAM_END;
-+	}
-+	return ev;
-+}
--- 
-2.53.0
-

diff --git a/0005-openssh-6.6p1-allow-ip-opts.patch b/0005-openssh-6.6p1-allow-ip-opts.patch
deleted file mode 100644
index 0243e81..0000000
--- a/0005-openssh-6.6p1-allow-ip-opts.patch
+++ /dev/null
@@ -1,56 +0,0 @@
-From 135bb6903b715012a8ae302022bb7a31d1879c6a Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 05/54] openssh-6.6p1-allow-ip-opts
-
-#https://bugzilla.mindrot.org/show_bug.cgi?id=1644
----
- sshd-session.c | 32 ++++++++++++++++++++++++++------
- 1 file changed, 26 insertions(+), 6 deletions(-)
-
-diff --git a/sshd-session.c b/sshd-session.c
-index 967737c5b..00d00e293 100644
---- a/sshd-session.c
-+++ b/sshd-session.c
-@@ -726,12 +726,32 @@ check_ip_options(struct ssh *ssh)
- 
- 	if (getsockopt(sock_in, IPPROTO_IP, IP_OPTIONS, opts,
- 	    &option_size) >= 0 && option_size != 0) {
--		text[0] = '\0';
--		for (i = 0; i < option_size; i++)
--			snprintf(text + i*3, sizeof(text) - i*3,
--			    " %2.2x", opts[i]);
--		fatal("Connection from %.100s port %d with IP opts: %.800s",
--		    ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text);
-+		i = 0;
-+		do {
-+			switch (opts[i]) {
-+				case 0:
-+				case 1:
-+					++i;
-+					break;
-+				case 130:
-+				case 133:
-+				case 134:
-+					if (i + 1 < option_size && opts[i + 1] >= 2) {
-+						i += opts[i + 1];
-+						break;
-+					}
-+					/* FALLTHROUGH */
-+				default:
-+				/* Fail, fatally, if we detect either loose or strict
-+			 	 * or incorrect source routing options. */
-+					text[0] = '\0';
-+					for (i = 0; i < option_size; i++)
-+						snprintf(text + i*3, sizeof(text) - i*3,
-+							" %2.2x", opts[i]);
-+					fatal("Connection from %.100s port %d with IP options:%.800s",
-+						ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text);
-+			}
-+		} while (i < option_size);
- 	}
- #endif /* IP_OPTIONS */
- }
--- 
-2.53.0
-

diff --git a/0005-openssh-6.6p1-keycat.patch b/0005-openssh-6.6p1-keycat.patch
new file mode 100644
index 0000000..7acb3c6
--- /dev/null
+++ b/0005-openssh-6.6p1-keycat.patch
@@ -0,0 +1,393 @@
+From 9f85e1dff3539c5791634a361f3c696d87ef9beb Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 05/54] openssh-6.6p1-keycat
+
+---
+ .depend          |   1 +
+ .gitignore       |   1 +
+ HOWTO.ssh-keycat |  12 +++
+ Makefile.in      |   8 +-
+ configure.ac     |   6 ++
+ ssh-keycat.c     | 241 +++++++++++++++++++++++++++++++++++++++++++++++
+ 6 files changed, 268 insertions(+), 1 deletion(-)
+ create mode 100644 HOWTO.ssh-keycat
+ create mode 100644 ssh-keycat.c
+
+diff --git a/.depend b/.depend
+index 03e8c423d..b6b1ad242 100644
+--- a/.depend
++++ b/.depend
+@@ -134,6 +134,7 @@ ssh-ed25519-sk.o: includes.h config.h defines.h platform.h openbsd-compat/openbs
+ ssh-ed25519.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/bsd-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-keygen.o: cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h
+ ssh-keygen.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/bsd-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 sshkey.h authfile.h sshbuf.h pathnames.h log.h ssherr.h misc.h match.h hostfile.h dns.h ssh.h ssh2.h atomicio.h krl.h digest.h utf8.h authfd.h sshsig.h ssh-sk.h sk-api.h cipher.h
++ssh-keycat.o: config.h uidswap.h misc.h
+ ssh-keyscan.o: atomicio.h misc.h hostfile.h ssh_api.h ssh2.h dns.h addr.h
+ ssh-keyscan.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/bsd-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 sshbuf.h sshkey.h cipher.h cipher-chachapoly.h chacha.h poly1305.h cipher-aesctr.h rijndael.h digest.h kex.h mac.h crypto_api.h compat.h myproposal.h packet.h dispatch.h log.h ssherr.h
+ ssh-keysign.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/bsd-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 log.h ssherr.h sshkey.h ssh.h ssh2.h misc.h sshbuf.h authfile.h msg.h canohost.h pathnames.h readconf.h uidswap.h
+diff --git a/.gitignore b/.gitignore
+index 907533067..7b4668d71 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -28,6 +28,7 @@ ssh-agent
+ ssh-keygen
+ ssh-keyscan
+ ssh-keysign
++ssh-keycat
+ ssh-pkcs11-helper
+ ssh-sk-helper
+ sshd
+diff --git a/HOWTO.ssh-keycat b/HOWTO.ssh-keycat
+new file mode 100644
+index 000000000..630ec628c
+--- /dev/null
++++ b/HOWTO.ssh-keycat
+@@ -0,0 +1,12 @@
++The ssh-keycat retrieves the content of the ~/.ssh/authorized_keys
++of an user in any environment. This includes environments with
++polyinstantiation of home directories and SELinux MLS policy enabled.
++
++To use ssh-keycat, set these options in /etc/ssh/sshd_config file:
++        AuthorizedKeysCommand /usr/libexec/openssh/ssh-keycat
++        AuthorizedKeysCommandUser root
++
++Do not forget to enable public key authentication:
++        PubkeyAuthentication yes
++
++
+diff --git a/Makefile.in b/Makefile.in
+index da59bcc10..c932373fc 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -23,6 +23,7 @@ SSH_PROGRAM=@bindir@/ssh
+ ASKPASS_PROGRAM=$(libexecdir)/ssh-askpass
+ SFTP_SERVER=$(libexecdir)/sftp-server
+ SSH_KEYSIGN=$(libexecdir)/ssh-keysign
++SSH_KEYCAT=$(libexecdir)/ssh-keycat
+ SSHD_SESSION=$(libexecdir)/sshd-session
+ SSHD_AUTH=$(libexecdir)/sshd-auth
+ SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
+@@ -58,6 +59,7 @@ CHANNELLIBS=@CHANNELLIBS@
+ K5LIBS=@K5LIBS@
+ GSSLIBS=@GSSLIBS@
+ SSHDLIBS=@SSHDLIBS@
++KEYCATLIBS=@KEYCATLIBS@
+ LIBEDIT=@LIBEDIT@
+ LIBFIDO2=@LIBFIDO2@
+ LIBWTMPDB=@LIBWTMPDB@
+@@ -75,7 +77,7 @@ MKDIR_P=@MKDIR_P@
+ 
+ .SUFFIXES: .lo
+ 
+-TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) sshd-session$(EXEEXT) sshd-auth$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-sk-helper$(EXEEXT) $(SK_STANDALONE)
++TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) sshd-session$(EXEEXT) sshd-auth$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-sk-helper$(EXEEXT) ssh-keycat$(EXEEXT) $(SK_STANDALONE)
+ 
+ LIBOPENSSH_OBJS=\
+ 	ssh_api.o \
+@@ -253,6 +255,9 @@ ssh-pkcs11-helper$(EXEEXT): $(LIBCOMPAT) libssh.a $(P11HELPER_OBJS)
+ ssh-sk-helper$(EXEEXT): $(LIBCOMPAT) libssh.a $(SKHELPER_OBJS)
+ 	$(LD) -o $@ $(SKHELPER_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(LIBS) $(LIBFIDO2) $(CHANNELLIBS)
+ 
++ssh-keycat$(EXEEXT): $(LIBCOMPAT) $(SSHDOBJS) libssh.a ssh-keycat.o uidswap.o
++	$(LD) -o $@ ssh-keycat.o uidswap.o $(LDFLAGS) -lssh -lopenbsd-compat $(KEYCATLIBS) $(CHANNELLIBS) $(LIBS)
++
+ ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHKEYSCAN_OBJS)
+ 	$(LD) -o $@ $(SSHKEYSCAN_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS) $(CHANNELLIBS)
+ 
+@@ -449,6 +454,7 @@ install-files:
+ 	$(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign$(EXEEXT) $(DESTDIR)$(SSH_KEYSIGN)$(EXEEXT)
+ 	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
+ 	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-sk-helper$(EXEEXT) $(DESTDIR)$(SSH_SK_HELPER)$(EXEEXT)
++	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-keycat$(EXEEXT) $(DESTDIR)$(libexecdir)/ssh-keycat$(EXEEXT)
+ 	$(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
+ 	$(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
+ 	$(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
+diff --git a/configure.ac b/configure.ac
+index a4d544cc5..d7e911e81 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -3700,6 +3700,7 @@ AC_ARG_WITH([pam],
+ 			PAM_MSG="yes"
+ 
+ 			SSHDLIBS="$SSHDLIBS -lpam"
++			KEYCATLIBS="$KEYCATLIBS -lpam"
+ 			AC_DEFINE([USE_PAM], [1],
+ 				[Define if you want to enable PAM support])
+ 
+@@ -3710,6 +3711,7 @@ AC_ARG_WITH([pam],
+ 					;;
+ 				*)
+ 					SSHDLIBS="$SSHDLIBS -ldl"
++					KEYCATLIBS="$KEYCATLIBS -ldl"
+ 					;;
+ 				esac
+ 			fi
+@@ -4953,6 +4955,7 @@ AC_ARG_WITH([selinux],
+ 	fi ]
+ )
+ AC_SUBST([SSHDLIBS])
++AC_SUBST([KEYCATLIBS])
+ 
+ # Check whether user wants Kerberos 5 support
+ KRB5_MSG="no"
+@@ -5966,6 +5969,9 @@ fi
+ if test ! -z "${SSHDLIBS}"; then
+ echo "         +for sshd: ${SSHDLIBS}"
+ fi
++if test ! -z "${KEYCATLIBS}"; then
++echo "   +for ssh-keycat: ${KEYCATLIBS}"
++fi
+ 
+ echo ""
+ 
+diff --git a/ssh-keycat.c b/ssh-keycat.c
+new file mode 100644
+index 000000000..8bb17c73e
+--- /dev/null
++++ b/ssh-keycat.c
+@@ -0,0 +1,241 @@
++/*
++ * 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, and the entire permission notice in its entirety,
++ *    including the disclaimer of warranties.
++ * 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.
++ * 3. The name of the author may not be used to endorse or promote
++ *    products derived from this software without specific prior
++ *    written permission.
++ *
++ * ALTERNATIVELY, this product may be distributed under the terms of
++ * the GNU Public License, in which case the provisions of the GPL are
++ * required INSTEAD OF the above restrictions.  (This clause is
++ * necessary due to a potential bad interaction between the GPL and
++ * the restrictions contained in a BSD-style copyright.)
++ *
++ * THIS SOFTWARE IS PROVIDED ``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.
++ */
++
++/*
++ * Copyright (c) 2011 Red Hat, Inc.
++ * Written by Tomas Mraz <tmraz@redhat.com>
++*/
++
++#define _GNU_SOURCE
++
++#include "config.h"
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <pwd.h>
++#include <fcntl.h>
++#include <unistd.h>
++#ifdef HAVE_STDINT_H
++#include <stdint.h>
++#endif
++
++#include <security/pam_appl.h>
++
++#include "uidswap.h"
++#include "misc.h"
++
++#define ERR_USAGE 1
++#define ERR_PAM_START 2
++#define ERR_OPEN_SESSION 3
++#define ERR_CLOSE_SESSION 4
++#define ERR_PAM_END 5
++#define ERR_GETPWNAM 6
++#define ERR_MEMORY 7
++#define ERR_OPEN 8
++#define ERR_FILE_MODE 9
++#define ERR_FDOPEN 10
++#define ERR_STAT 11
++#define ERR_WRITE 12
++#define ERR_PAM_PUTENV 13
++#define BUFLEN 4096
++
++/* Just ignore the messages in the conversation function */
++static int
++dummy_conv(int num_msg, const struct pam_message **msgm,
++	   struct pam_response **response, void *appdata_ptr)
++{
++	struct pam_response *rsp;
++
++	(void)msgm;
++	(void)appdata_ptr;
++
++	if (num_msg <= 0)
++		return PAM_CONV_ERR;
++
++	/* Just allocate the array as empty responses */
++	rsp = calloc (num_msg, sizeof (struct pam_response));
++	if (rsp == NULL)
++		return PAM_CONV_ERR;
++
++	*response = rsp;
++	return PAM_SUCCESS;
++}
++
++static struct pam_conv conv = {
++	dummy_conv,
++	NULL
++};
++
++char *
++make_auth_keys_name(const struct passwd *pwd)
++{
++	char *fname;
++
++	if (asprintf(&fname, "%s/.ssh/authorized_keys", pwd->pw_dir) < 0)
++		return NULL;
++
++	return fname;
++}
++
++int
++dump_keys(const char *user)
++{
++	struct passwd *pwd;
++	int fd = -1;
++	FILE *f = NULL;
++	char *fname = NULL;
++	int rv = 0;
++	char buf[BUFLEN];
++	size_t len;
++	struct stat st;
++
++	if ((pwd = getpwnam(user)) == NULL) {
++		return ERR_GETPWNAM;
++	}
++
++	if ((fname = make_auth_keys_name(pwd)) == NULL) {
++		return ERR_MEMORY;
++	}
++
++	temporarily_use_uid(pwd);
++
++	if ((fd = open(fname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < 0) {
++		rv = ERR_OPEN;
++		goto fail;
++	}
++
++	if (fstat(fd, &st) < 0) {
++		rv = ERR_STAT;
++		goto fail;
++	}
++
++	if (!S_ISREG(st.st_mode) ||
++		(st.st_uid != pwd->pw_uid && st.st_uid != 0)) {
++		rv = ERR_FILE_MODE;
++		goto fail;
++	}
++
++	unset_nonblock(fd);
++
++	if ((f = fdopen(fd, "r")) == NULL) {
++		rv = ERR_FDOPEN;
++		goto fail;
++	}
++
++	fd = -1;
++
++	while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
++		rv = fwrite(buf, 1, len, stdout) != len ? ERR_WRITE : 0;
++	}
++
++fail:
++	if (fd != -1)
++		close(fd);
++	if (f != NULL)
++		fclose(f);
++	free(fname);
++	restore_uid();
++	return rv;
++}
++
++static const char *env_names[] = { "SELINUX_ROLE_REQUESTED",
++	"SELINUX_LEVEL_REQUESTED",
++	"SELINUX_USE_CURRENT_RANGE"
++};
++
++extern char **environ;
++
++int
++set_pam_environment(pam_handle_t *pamh)
++{
++	int i;
++	size_t j;
++
++	for (j = 0; j < sizeof(env_names)/sizeof(env_names[0]); ++j) {
++		int len = strlen(env_names[j]);
++
++		for (i = 0; environ[i] != NULL; ++i) {
++			if (strncmp(env_names[j], environ[i], len) == 0 &&
++			    environ[i][len] == '=') {
++				if (pam_putenv(pamh, environ[i]) != PAM_SUCCESS)
++					return ERR_PAM_PUTENV;
++			}
++		}
++	}
++
++	return 0;
++}
++
++int
++main(int argc, char *argv[])
++{
++	pam_handle_t *pamh = NULL;
++	int retval;
++	int ev = 0;
++
++	if (argc != 2) {
++		fprintf(stderr, "Usage: %s <user-name>\n", argv[0]);
++		return ERR_USAGE;
++	}
++
++	retval = pam_start("ssh-keycat", argv[1], &conv, &pamh);
++	if (retval != PAM_SUCCESS) {
++		return ERR_PAM_START;
++	}
++
++	ev = set_pam_environment(pamh);
++	if (ev != 0)
++		goto finish;
++
++	retval = pam_open_session(pamh, PAM_SILENT);
++	if (retval != PAM_SUCCESS) {
++		ev = ERR_OPEN_SESSION;
++		goto finish;
++	}
++
++	ev = dump_keys(argv[1]);
++
++	retval = pam_close_session(pamh, PAM_SILENT);
++	if (retval != PAM_SUCCESS) {
++		ev = ERR_CLOSE_SESSION;
++	}
++
++finish:
++	retval = pam_end (pamh,retval);
++	if (retval != PAM_SUCCESS) {
++		ev = ERR_PAM_END;
++	}
++	return ev;
++}
+-- 
+2.55.0
+

diff --git a/0006-openssh-5.9p1-ipv6man.patch b/0006-openssh-5.9p1-ipv6man.patch
deleted file mode 100644
index 2bb7ae8..0000000
--- a/0006-openssh-5.9p1-ipv6man.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-From ba20c2380b3108b1f8e2c00689a8efc6623cd481 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 06/54] openssh-5.9p1-ipv6man
-
-#(drop?) https://bugzilla.mindrot.org/show_bug.cgi?id=1925
----
- ssh.1  | 2 ++
- sshd.8 | 2 ++
- 2 files changed, 4 insertions(+)
-
-diff --git a/ssh.1 b/ssh.1
-index 82ae5480c..c2c8f50f1 100644
---- a/ssh.1
-+++ b/ssh.1
-@@ -1669,6 +1669,8 @@ manual page for more information.
- .Nm
- exits with the exit status of the remote command or with 255
- if an error occurred.
-+.Sh IPV6
-+IPv6 address can be used everywhere where IPv4 address. In all entries must be the IPv6 address enclosed in square brackets. Note: The square brackets are metacharacters for the shell and must be escaped in shell.
- .Sh SEE ALSO
- .Xr scp 1 ,
- .Xr sftp 1 ,
-diff --git a/sshd.8 b/sshd.8
-index 7fbca776a..0226a8303 100644
---- a/sshd.8
-+++ b/sshd.8
-@@ -1018,6 +1018,8 @@ concurrently for different ports, this contains the process ID of the one
- started last).
- The content of this file is not sensitive; it can be world-readable.
- .El
-+.Sh IPV6
-+IPv6 address can be used everywhere where IPv4 address. In all entries must be the IPv6 address enclosed in square brackets. Note: The square brackets are metacharacters for the shell and must be escaped in shell.
- .Sh SEE ALSO
- .Xr scp 1 ,
- .Xr sftp 1 ,
--- 
-2.53.0
-

diff --git a/0006-openssh-6.6p1-allow-ip-opts.patch b/0006-openssh-6.6p1-allow-ip-opts.patch
new file mode 100644
index 0000000..392bec2
--- /dev/null
+++ b/0006-openssh-6.6p1-allow-ip-opts.patch
@@ -0,0 +1,56 @@
+From 87f6d5b0348b109074f77c8f6f9d2ae49d481f68 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 06/54] openssh-6.6p1-allow-ip-opts
+
+#https://bugzilla.mindrot.org/show_bug.cgi?id=1644
+---
+ sshd-session.c | 32 ++++++++++++++++++++++++++------
+ 1 file changed, 26 insertions(+), 6 deletions(-)
+
+diff --git a/sshd-session.c b/sshd-session.c
+index d7d4f32c5..fea94dc6c 100644
+--- a/sshd-session.c
++++ b/sshd-session.c
+@@ -727,12 +727,32 @@ check_ip_options(struct ssh *ssh)
+ 
+ 	if (getsockopt(sock_in, IPPROTO_IP, IP_OPTIONS, opts,
+ 	    &option_size) >= 0 && option_size != 0) {
+-		text[0] = '\0';
+-		for (i = 0; i < option_size; i++)
+-			snprintf(text + i*3, sizeof(text) - i*3,
+-			    " %2.2x", opts[i]);
+-		fatal("Connection from %.100s port %d with IP opts: %.800s",
+-		    ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text);
++		i = 0;
++		do {
++			switch (opts[i]) {
++				case 0:
++				case 1:
++					++i;
++					break;
++				case 130:
++				case 133:
++				case 134:
++					if (i + 1 < option_size && opts[i + 1] >= 2) {
++						i += opts[i + 1];
++						break;
++					}
++					/* FALLTHROUGH */
++				default:
++				/* Fail, fatally, if we detect either loose or strict
++			 	 * or incorrect source routing options. */
++					text[0] = '\0';
++					for (i = 0; i < option_size; i++)
++						snprintf(text + i*3, sizeof(text) - i*3,
++							" %2.2x", opts[i]);
++					fatal("Connection from %.100s port %d with IP options:%.800s",
++						ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text);
++			}
++		} while (i < option_size);
+ 	}
+ #endif /* IP_OPTIONS */
+ }
+-- 
+2.55.0
+

diff --git a/0007-openssh-5.8p2-sigpipe.patch b/0007-openssh-5.8p2-sigpipe.patch
deleted file mode 100644
index 1db2d86..0000000
--- a/0007-openssh-5.8p2-sigpipe.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-From e15cc73c1a26dcfab3fd0077ee0561724ad4f8b8 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Thu, 15 May 2025 13:43:28 +0200
-Subject: [PATCH 07/54] openssh-5.8p2-sigpipe
-
----
- ssh-keyscan.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/ssh-keyscan.c b/ssh-keyscan.c
-index 9bd3e78eb..bb3ee462d 100644
---- a/ssh-keyscan.c
-+++ b/ssh-keyscan.c
-@@ -777,6 +777,9 @@ main(int argc, char **argv)
- 	if (maxfd > fdlim_get(0))
- 		fdlim_set(maxfd);
- 	fdcon = xcalloc(maxfd, sizeof(con));
-+ 
-+	signal(SIGPIPE, SIG_IGN);
-+
- 	read_wait = xcalloc(maxfd, sizeof(struct pollfd));
- 	for (j = 0; j < maxfd; j++)
- 		read_wait[j].fd = -1;
--- 
-2.53.0
-

diff --git a/0007-openssh-5.9p1-ipv6man.patch b/0007-openssh-5.9p1-ipv6man.patch
new file mode 100644
index 0000000..d7a5864
--- /dev/null
+++ b/0007-openssh-5.9p1-ipv6man.patch
@@ -0,0 +1,40 @@
+From f0d07631a0b6312bf35aced2c9629b74d70d81e1 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 07/54] openssh-5.9p1-ipv6man
+
+#(drop?) https://bugzilla.mindrot.org/show_bug.cgi?id=1925
+---
+ ssh.1  | 2 ++
+ sshd.8 | 2 ++
+ 2 files changed, 4 insertions(+)
+
+diff --git a/ssh.1 b/ssh.1
+index 22f385f06..28eaeac2f 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -1670,6 +1670,8 @@ manual page for more information.
+ .Nm
+ exits with the exit status of the remote command or with 255
+ if an error occurred.
++.Sh IPV6
++IPv6 address can be used everywhere where IPv4 address. In all entries must be the IPv6 address enclosed in square brackets. Note: The square brackets are metacharacters for the shell and must be escaped in shell.
+ .Sh SEE ALSO
+ .Xr scp 1 ,
+ .Xr sftp 1 ,
+diff --git a/sshd.8 b/sshd.8
+index 7fbca776a..0226a8303 100644
+--- a/sshd.8
++++ b/sshd.8
+@@ -1018,6 +1018,8 @@ concurrently for different ports, this contains the process ID of the one
+ started last).
+ The content of this file is not sensitive; it can be world-readable.
+ .El
++.Sh IPV6
++IPv6 address can be used everywhere where IPv4 address. In all entries must be the IPv6 address enclosed in square brackets. Note: The square brackets are metacharacters for the shell and must be escaped in shell.
+ .Sh SEE ALSO
+ .Xr scp 1 ,
+ .Xr sftp 1 ,
+-- 
+2.55.0
+

diff --git a/0008-openssh-5.8p2-sigpipe.patch b/0008-openssh-5.8p2-sigpipe.patch
new file mode 100644
index 0000000..7ccfd8d
--- /dev/null
+++ b/0008-openssh-5.8p2-sigpipe.patch
@@ -0,0 +1,26 @@
+From 4a68f641e71153c825f5cdc34d0b3a07022df638 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 08/54] openssh-5.8p2-sigpipe
+
+---
+ ssh-keyscan.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/ssh-keyscan.c b/ssh-keyscan.c
+index ddaf8dfb1..c455ef532 100644
+--- a/ssh-keyscan.c
++++ b/ssh-keyscan.c
+@@ -786,6 +786,9 @@ main(int argc, char **argv)
+ 	if (maxfd > fdlim_get(0))
+ 		fdlim_set(maxfd);
+ 	fdcon = xcalloc(maxfd, sizeof(con));
++ 
++	signal(SIGPIPE, SIG_IGN);
++
+ 	read_wait = xcalloc(maxfd, sizeof(struct pollfd));
+ 	for (j = 0; j < maxfd; j++)
+ 		read_wait[j].fd = -1;
+-- 
+2.55.0
+

diff --git a/0009-openssh-5.1p1-askpass-progress.patch b/0009-openssh-5.1p1-askpass-progress.patch
index 7b3674b..c96a7d4 100644
--- a/0009-openssh-5.1p1-askpass-progress.patch
+++ b/0009-openssh-5.1p1-askpass-progress.patch
@@ -1,4 +1,4 @@
-From 4314d288db2a814ad58e91da21d2a69e4983d3c1 Mon Sep 17 00:00:00 2001
+From fd8ad7a3a2b5d47aa989c619cb12c7f681853f9b Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 09/54] openssh-5.1p1-askpass-progress
@@ -92,5 +92,5 @@ index a62f98152..cb7152dc8 100644
  
  	/* Grab focus */
 -- 
-2.53.0
+2.55.0
 

diff --git a/0010-openssh-4.3p2-askpass-grab-info.patch b/0010-openssh-4.3p2-askpass-grab-info.patch
index 61223ae..967cf3c 100644
--- a/0010-openssh-4.3p2-askpass-grab-info.patch
+++ b/0010-openssh-4.3p2-askpass-grab-info.patch
@@ -1,4 +1,4 @@
-From db4ca336e4bddfc4c5ce651fbdf1f40348d93b8f Mon Sep 17 00:00:00 2001
+From cfdb0456fe83c0df2dacefa8cbe31cde96eca0cf Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 10/54] openssh-4.3p2-askpass-grab-info
@@ -28,5 +28,5 @@ index cb7152dc8..bbe93d838 100644
  
  	gtk_dialog_run(GTK_DIALOG(err));
 -- 
-2.53.0
+2.55.0
 

diff --git a/0011-openssh-8.7p1-redhat.patch b/0011-openssh-8.7p1-redhat.patch
index 5ab2485..345160f 100644
--- a/0011-openssh-8.7p1-redhat.patch
+++ b/0011-openssh-8.7p1-redhat.patch
@@ -1,4 +1,4 @@
-From ec63856f2c4276da3aa22c78feacfebfbfb594dc Mon Sep 17 00:00:00 2001
+From 62cef37ebf112f4389a520e7277fd7cc30a7019c Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 11/54] openssh-8.7p1-redhat
@@ -76,10 +76,10 @@ index 0f4a3a724..608203e4b 100644
  #AddressFamily any
  #ListenAddress 0.0.0.0
 diff --git a/sshd_config.0 b/sshd_config.0
-index 770ec742e..1f71c37c3 100644
+index 9bafd243e..dc685568a 100644
 --- a/sshd_config.0
 +++ b/sshd_config.0
-@@ -1247,9 +1247,9 @@ DESCRIPTION
+@@ -1255,9 +1255,9 @@ DESCRIPTION
  
       SyslogFacility
               Gives the facility code that is used when logging messages from
@@ -93,10 +93,10 @@ index 770ec742e..1f71c37c3 100644
       TCPKeepAlive
               Specifies whether the system should send TCP keepalive messages
 diff --git a/sshd_config.5 b/sshd_config.5
-index 3f5e29812..ca425a8da 100644
+index 39a864dee..3896d21b0 100644
 --- a/sshd_config.5
 +++ b/sshd_config.5
-@@ -1971,7 +1971,7 @@ By default no subsystems are defined.
+@@ -1979,7 +1979,7 @@ By default no subsystems are defined.
  .It Cm SyslogFacility
  Gives the facility code that is used when logging messages from
  .Xr sshd 8 .
@@ -143,5 +143,5 @@ index 000000000..1d592d13f
 +Include /etc/crypto-policies/back-ends/opensshserver.config
 +
 -- 
-2.53.0
+2.55.0
 

diff --git a/0012-openssh-7.8p1-UsePAM-warning.patch b/0012-openssh-7.8p1-UsePAM-warning.patch
index 831535a..a8e0704 100644
--- a/0012-openssh-7.8p1-UsePAM-warning.patch
+++ b/0012-openssh-7.8p1-UsePAM-warning.patch
@@ -1,4 +1,4 @@
-From 98092c1fa5c54b32edcb7cef18a4fab6078efbc0 Mon Sep 17 00:00:00 2001
+From e0b43c6be9a94437c33239455815a70c287ca5f5 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 12/54] openssh-7.8p1-UsePAM-warning
@@ -10,10 +10,10 @@ Subject: [PATCH 12/54] openssh-7.8p1-UsePAM-warning
  2 files changed, 6 insertions(+)
 
 diff --git a/sshd-session.c b/sshd-session.c
-index 00d00e293..6976d6872 100644
+index fea94dc6c..8c6652152 100644
 --- a/sshd-session.c
 +++ b/sshd-session.c
-@@ -1075,6 +1075,10 @@ main(int ac, char **av)
+@@ -1076,6 +1076,10 @@ main(int ac, char **av)
  			    "enabled authentication methods");
  	}
  
@@ -38,5 +38,5 @@ index 608203e4b..48af6321b 100644
  
  #AllowAgentForwarding yes
 -- 
-2.53.0
+2.55.0
 

diff --git a/0013-openssh-9.6p1-gssapi-keyex.patch b/0013-openssh-9.6p1-gssapi-keyex.patch
index f2106a4..457fce3 100644
--- a/0013-openssh-9.6p1-gssapi-keyex.patch
+++ b/0013-openssh-9.6p1-gssapi-keyex.patch
@@ -1,9 +1,10 @@
-From 208080ec2ceddb9e4c917d4e57b9bc4452db3049 Mon Sep 17 00:00:00 2001
+From c4d7da173ac8b84dfef9d2310d615c0249bcd37a Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 13/54] openssh-9.6p1-gssapi-keyex
 
 ---
+ .depend         |   2 +
  Makefile.in     |   7 +-
  auth.c          |   3 +-
  auth2-gss.c     |  52 +++-
@@ -16,7 +17,7 @@ Subject: [PATCH 13/54] openssh-9.6p1-gssapi-keyex
  gss-genr.c      | 312 ++++++++++++++++++++-
  gss-serv-krb5.c |  97 ++++++-
  gss-serv.c      | 200 ++++++++++++--
- kex-names.c     |  61 ++++-
+ kex-names.c     |  67 ++++-
  kex.c           |  35 ++-
  kex.h           |  34 +++
  kexdh.c         |  10 +
@@ -29,31 +30,44 @@ Subject: [PATCH 13/54] openssh-9.6p1-gssapi-keyex
  monitor_wrap.h  |   4 +-
  readconf.c      |  70 +++++
  readconf.h      |   6 +
- servconf.c      |  46 ++++
- servconf.h      |   3 +
+ servconf.c      |  28 ++
+ servconf.h      |  11 +-
  session.c       |  10 +-
  ssh-gss.h       |  64 ++++-
  ssh.1           |   8 +
- ssh.c           |   6 +-
+ ssh.c           |   7 +-
  ssh_config      |   2 +
- ssh_config.5    |  58 ++++
+ ssh_config.5    |  61 +++++
  sshconnect2.c   | 154 ++++++++++-
  sshd-auth.c     |  55 +++-
  sshd-session.c  |   9 +-
  sshd.c          |   3 +-
  sshd_config     |   2 +
- sshd_config.5   |  40 ++-
+ sshd_config.5   |  43 ++-
  sshkey.c        |  72 ++++-
  sshkey.h        |   1 +
- 41 files changed, 3004 insertions(+), 78 deletions(-)
+ 42 files changed, 3007 insertions(+), 80 deletions(-)
  create mode 100644 kexgssc.c
  create mode 100644 kexgsss.c
 
+diff --git a/.depend b/.depend
+index b6b1ad242..7e5f3dcd3 100644
+--- a/.depend
++++ b/.depend
+@@ -67,6 +67,8 @@ kexc25519.o: includes.h config.h defines.h platform.h openbsd-compat/openbsd-com
+ kexdh.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/bsd-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
+ kexecdh.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/bsd-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 ssherr.h
+ kexgen.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/bsd-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 sshkey.h kex.h mac.h crypto_api.h log.h ssherr.h packet.h dispatch.h ssh2.h sshbuf.h digest.h
++kexgssc.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/bsd-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
++kexgsss.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/bsd-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
+ kexgex.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/bsd-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
+ kexgexc.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/bsd-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
+ kexgexs.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/bsd-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
 diff --git a/Makefile.in b/Makefile.in
-index 42b45d33d..a12f6ad1d 100644
+index c932373fc..106306024 100644
 --- a/Makefile.in
 +++ b/Makefile.in
-@@ -108,6 +108,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+@@ -109,6 +109,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
  	kex.o kex-names.o kexdh.o kexgex.o kexecdh.o kexc25519.o \
  	kexgexc.o kexgexs.o \
  	kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \
@@ -61,7 +75,7 @@ index 42b45d33d..a12f6ad1d 100644
  	sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
  	sshbuf-io.o misc-agent.o ssherr-libcrypto.o
  
-@@ -131,7 +132,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rhosts.o auth-passwd.o \
+@@ -132,7 +133,7 @@ SSHD_SESSION_OBJS=sshd-session.o auth-rhosts.o auth-passwd.o \
  	auth2-chall.o groupaccess.o \
  	auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
  	auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
@@ -70,7 +84,7 @@ index 42b45d33d..a12f6ad1d 100644
  	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 \
-@@ -143,7 +144,7 @@ SSHD_AUTH_OBJS=sshd-auth.o \
+@@ -144,7 +145,7 @@ SSHD_AUTH_OBJS=sshd-auth.o \
  	serverloop.o auth.o auth2.o auth-options.o session.o auth2-chall.o \
  	groupaccess.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
  	auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-pubkeyfile.o \
@@ -79,7 +93,7 @@ index 42b45d33d..a12f6ad1d 100644
  	monitor_wrap.o auth-krb5.o \
  	audit.o audit-bsm.o audit-linux.o platform.o \
  	loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
-@@ -555,7 +556,7 @@ regress-prep:
+@@ -566,7 +567,7 @@ regress-prep:
  	    ln -s `cd $(srcdir) && pwd`/regress/Makefile `pwd`/regress/Makefile
  
  REGRESSLIBS=libssh.a $(LIBCOMPAT)
@@ -103,7 +117,7 @@ index a0217a811..142ac5afd 100644
  		break;
  	case PERMIT_FORCED_ONLY:
 diff --git a/auth2-gss.c b/auth2-gss.c
-index b380dc117..90f099e85 100644
+index dfee5613e..bb9c80bd4 100644
 --- a/auth2-gss.c
 +++ b/auth2-gss.c
 @@ -51,6 +51,7 @@
@@ -163,16 +177,16 @@ index b380dc117..90f099e85 100644
  /*
   * We only support those mechanisms that we know about (ie ones that we know
   * how to check local user kuserok and the like)
-@@ -267,7 +310,7 @@ input_gssapi_exchange_complete(int type, uint32_t plen, struct ssh *ssh)
+@@ -263,7 +306,7 @@ input_gssapi_exchange_complete(int type, uint32_t plen, struct ssh *ssh)
  	if ((r = sshpkt_get_end(ssh)) != 0)
  		fatal_fr(r, "parse packet");
  
 -	authenticated = mm_ssh_gssapi_userok(authctxt->user);
 + 	authenticated = mm_ssh_gssapi_userok(authctxt->user, authctxt->pw, 1);
+ 	if (!authenticated)
+ 		auth_failure_delay(authctxt, tstart);
  
- 	authctxt->postponed = 0;
- 	ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
-@@ -315,7 +358,7 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
+@@ -314,7 +357,7 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
  	gssbuf.length = sshbuf_len(b);
  
  	if (!GSS_ERROR(mm_ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic)))
@@ -181,7 +195,7 @@ index b380dc117..90f099e85 100644
  	else
  		logit("GSSAPI MIC check failed");
  
-@@ -333,6 +376,11 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
+@@ -335,6 +378,11 @@ input_gssapi_mic(int type, uint32_t plen, struct ssh *ssh)
  	return 0;
  }
  
@@ -218,7 +232,7 @@ index 99637a89b..a05908cf3 100644
  #endif
  	&methodcfg_passwd,
 diff --git a/auth2.c b/auth2.c
-index bfd425eab..3c3bdb776 100644
+index 497fa8ed2..f0f39707f 100644
 --- a/auth2.c
 +++ b/auth2.c
 @@ -71,6 +71,7 @@ extern Authmethod method_passwd;
@@ -357,7 +371,7 @@ index 26d62855a..0cadc9f18 100644
  int		 get_peer_port(int);
  char		*get_local_ipaddr(int);
 diff --git a/clientloop.c b/clientloop.c
-index 6a0e7b6b8..ab5c4d4ad 100644
+index 49950cecc..f076d1d56 100644
 --- a/clientloop.c
 +++ b/clientloop.c
 @@ -101,6 +101,10 @@
@@ -371,7 +385,7 @@ index 6a0e7b6b8..ab5c4d4ad 100644
  /* Permitted RSA signature algorithms for UpdateHostkeys proofs */
  #define HOSTKEY_PROOF_RSA_ALGS	"rsa-sha2-512,rsa-sha2-256"
  
-@@ -1611,6 +1615,14 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
+@@ -1617,6 +1621,14 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
  		/* Do channel operations. */
  		channel_after_poll(ssh, pfd, npfd_active);
  
@@ -387,7 +401,7 @@ index 6a0e7b6b8..ab5c4d4ad 100644
  		if (conn_in_ready)
  			client_process_net_input(ssh);
 diff --git a/configure.ac b/configure.ac
-index 8f8276c78..75576b45d 100644
+index d7e911e81..321adbab1 100644
 --- a/configure.ac
 +++ b/configure.ac
 @@ -844,6 +844,30 @@ int main(void) { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16))
@@ -1256,7 +1270,7 @@ index f9ae303b5..8b4fa9cfe 100644
  
  /* Privileged */
 diff --git a/kex-names.c b/kex-names.c
-index 751f06cea..769a36810 100644
+index 751f06cea..3bc38130b 100644
 --- a/kex-names.c
 +++ b/kex-names.c
 @@ -36,6 +36,7 @@
@@ -1346,7 +1360,7 @@ index 751f06cea..769a36810 100644
  		if (kex_alg_by_name(p) == NULL) {
  			error("Unsupported KEX algorithm \"%.100s\"", p);
  			free(s);
-@@ -334,3 +368,26 @@ kex_assemble_names(char **listp, const char *def, const char *all)
+@@ -334,3 +374,26 @@ kex_assemble_names(char **listp, const char *def, const char *all)
  	free(ret);
  	return r;
  }
@@ -1374,7 +1388,7 @@ index 751f06cea..769a36810 100644
 +	return 1;
 +}
 diff --git a/kex.c b/kex.c
-index 85b112c75..3608a5504 100644
+index 11b271d47..3701a40c0 100644
 --- a/kex.c
 +++ b/kex.c
 @@ -293,17 +293,37 @@ static int
@@ -1421,7 +1435,7 @@ index 85b112c75..3608a5504 100644
  	    (r = sshbuf_put_cstring(m, "0")) != 0 ||
  	    (r = sshbuf_put_cstring(m, "agent-forward")) != 0 ||
  	    (r = sshbuf_put_cstring(m, "0")) != 0) {
-@@ -741,6 +761,9 @@ kex_free(struct kex *kex)
+@@ -746,6 +766,9 @@ kex_free(struct kex *kex)
  	sshbuf_free(kex->server_version);
  	sshbuf_free(kex->client_pub);
  	sshbuf_free(kex->session_id);
@@ -1432,7 +1446,7 @@ index 85b112c75..3608a5504 100644
  	sshkey_free(kex->initial_hostkey);
  	free(kex->failed_choice);
 diff --git a/kex.h b/kex.h
-index 4f6d92164..2c86b25f9 100644
+index a4d50f1a3..d229df7e6 100644
 --- a/kex.h
 +++ b/kex.h
 @@ -29,6 +29,10 @@
@@ -1462,7 +1476,7 @@ index 4f6d92164..2c86b25f9 100644
  	KEX_MAX
  };
  
-@@ -170,6 +183,13 @@ struct kex {
+@@ -171,6 +184,13 @@ struct kex {
  	u_int	flags;
  	int	hash_alg;
  	int	ec_nid;
@@ -1476,7 +1490,7 @@ index 4f6d92164..2c86b25f9 100644
  	char	*failed_choice;
  	int	(*verify_host_key)(struct sshkey *, struct ssh *);
  	struct sshkey *(*load_host_public_key)(int, int, struct ssh *);
-@@ -197,8 +217,10 @@ int	 kex_nid_from_name(const char *);
+@@ -198,8 +218,10 @@ int	 kex_nid_from_name(const char *);
  int	 kex_is_pq_from_name(const char *);
  int	 kex_names_valid(const char *);
  char	*kex_alg_list(char);
@@ -1487,7 +1501,7 @@ index 4f6d92164..2c86b25f9 100644
  int	 kex_assemble_names(char **, const char *, const char *);
  void	 kex_proposal_populate_entries(struct ssh *, char *prop[PROPOSAL_MAX],
      const char *, const char *, const char *, const char *, const char *);
-@@ -232,6 +254,12 @@ int	 kexgex_client(struct ssh *);
+@@ -233,6 +255,12 @@ int	 kexgex_client(struct ssh *);
  int	 kexgex_server(struct ssh *);
  int	 kex_gen_client(struct ssh *);
  int	 kex_gen_server(struct ssh *);
@@ -1500,7 +1514,7 @@ index 4f6d92164..2c86b25f9 100644
  
  int	 kex_dh_keypair(struct kex *);
  int	 kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **,
-@@ -270,6 +298,12 @@ int	 kexgex_hash(int, const struct sshbuf *, const struct sshbuf *,
+@@ -271,6 +299,12 @@ int	 kexgex_hash(int, const struct sshbuf *, const struct sshbuf *,
      const BIGNUM *, const u_char *, size_t,
      u_char *, size_t *);
  
@@ -2876,7 +2890,7 @@ index 000000000..98e9404df
 +
 +#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
 diff --git a/monitor.c b/monitor.c
-index 1e41dfa53..025c6abbe 100644
+index 6fe9f46fc..cddbc0a22 100644
 --- a/monitor.c
 +++ b/monitor.c
 @@ -138,6 +138,8 @@ int mm_answer_gss_setup_ctx(struct ssh *, int, struct sshbuf *);
@@ -2946,7 +2960,7 @@ index 1e41dfa53..025c6abbe 100644
  
  	if (auth_opts->permit_pty_flag) {
  		monitor_permit(mon_dispatch, MONITOR_REQ_PTY, 1);
-@@ -1924,6 +1948,17 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
+@@ -1898,6 +1922,17 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
  # ifdef OPENSSL_HAS_ECC
  	kex->kex[KEX_ECDH_SHA2] = kex_gen_server;
  # endif
@@ -2964,7 +2978,7 @@ index 1e41dfa53..025c6abbe 100644
  #endif /* WITH_OPENSSL */
  	kex->kex[KEX_C25519_SHA256] = kex_gen_server;
  	kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
-@@ -2022,8 +2057,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1996,8 +2031,8 @@ mm_answer_gss_setup_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
  	u_char *p;
  	int r;
  
@@ -2975,7 +2989,7 @@ index 1e41dfa53..025c6abbe 100644
  
  	if ((r = sshbuf_get_string(m, &p, &len)) != 0)
  		fatal_fr(r, "parse");
-@@ -2055,8 +2090,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2029,8 +2064,8 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
  	OM_uint32 flags = 0; /* GSI needs this */
  	int r;
  
@@ -2986,7 +3000,7 @@ index 1e41dfa53..025c6abbe 100644
  
  	if ((r = ssh_gssapi_get_buffer_desc(m, &in)) != 0)
  		fatal_fr(r, "ssh_gssapi_get_buffer_desc");
-@@ -2076,6 +2111,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2050,6 +2085,7 @@ mm_answer_gss_accept_ctx(struct ssh *ssh, int sock, struct sshbuf *m)
  		monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0);
  		monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1);
  		monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1);
@@ -2994,7 +3008,7 @@ index 1e41dfa53..025c6abbe 100644
  	}
  	return (0);
  }
-@@ -2087,8 +2123,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2061,8 +2097,8 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
  	OM_uint32 ret;
  	int r;
  
@@ -3005,7 +3019,7 @@ index 1e41dfa53..025c6abbe 100644
  
  	if ((r = ssh_gssapi_get_buffer_desc(m, &gssbuf)) != 0 ||
  	    (r = ssh_gssapi_get_buffer_desc(m, &mic)) != 0)
-@@ -2114,13 +2150,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2088,13 +2124,17 @@ mm_answer_gss_checkmic(struct ssh *ssh, int sock, struct sshbuf *m)
  int
  mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
  {
@@ -3027,7 +3041,7 @@ index 1e41dfa53..025c6abbe 100644
  
  	sshbuf_reset(m);
  	if ((r = sshbuf_put_u32(m, authenticated)) != 0)
-@@ -2129,7 +2169,11 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2103,7 +2143,11 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
  	debug3_f("sending result %d", authenticated);
  	mm_request_send(sock, MONITOR_ANS_GSSUSEROK, m);
  
@@ -3040,7 +3054,7 @@ index 1e41dfa53..025c6abbe 100644
  
  	if ((displayname = ssh_gssapi_displayname()) != NULL)
  		auth2_record_info(authctxt, "%s", displayname);
-@@ -2137,5 +2181,84 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -2111,5 +2155,84 @@ mm_answer_gss_userok(struct ssh *ssh, int sock, struct sshbuf *m)
  	/* Monitor loop will terminate if authenticated */
  	return (authenticated);
  }
@@ -3139,10 +3153,10 @@ index 1b46e794e..75a0d6181 100644
  
  struct ssh;
 diff --git a/monitor_wrap.c b/monitor_wrap.c
-index f3c341bc7..496de436e 100644
+index b5aeb8b2c..2d905c16a 100644
 --- a/monitor_wrap.c
 +++ b/monitor_wrap.c
-@@ -1162,13 +1162,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
+@@ -1110,13 +1110,15 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
  }
  
  int
@@ -3159,7 +3173,7 @@ index f3c341bc7..496de436e 100644
  
  	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, m);
  	mm_request_receive_expect(pmonitor->m_recvfd,
-@@ -1181,6 +1183,59 @@ mm_ssh_gssapi_userok(char *user)
+@@ -1129,6 +1131,59 @@ mm_ssh_gssapi_userok(char *user)
  	debug3_f("user %sauthenticated", authenticated ? "" : "not ");
  	return (authenticated);
  }
@@ -3220,7 +3234,7 @@ index f3c341bc7..496de436e 100644
  
  /*
 diff --git a/monitor_wrap.h b/monitor_wrap.h
-index 3e08ddd78..a708bf4d8 100644
+index 30585187c..07b207d1d 100644
 --- a/monitor_wrap.h
 +++ b/monitor_wrap.h
 @@ -72,8 +72,10 @@ void mm_decode_activate_server_options(struct ssh *ssh, struct sshbuf *m);
@@ -3236,7 +3250,7 @@ index 3e08ddd78..a708bf4d8 100644
  
  #ifdef USE_PAM
 diff --git a/readconf.c b/readconf.c
-index 10cbd04ba..9004076e4 100644
+index 0218ed128..1b7d4ae4e 100644
 --- a/readconf.c
 +++ b/readconf.c
 @@ -57,6 +57,7 @@
@@ -3389,7 +3403,7 @@ index dbcb41725..8b0c98acf 100644
  						 * authentication. */
  	int     kbd_interactive_authentication; /* Try keyboard-interactive auth. */
 diff --git a/servconf.c b/servconf.c
-index 668259a20..de3fa9481 100644
+index 9b443bea0..6700f5d51 100644
 --- a/servconf.c
 +++ b/servconf.c
 @@ -62,6 +62,7 @@
@@ -3398,95 +3412,40 @@ index 668259a20..de3fa9481 100644
  #include "version.h"
 +#include "ssh-gss.h"
  
- #if !defined(SSHD_PAM_SERVICE)
- # define SSHD_PAM_SERVICE		"sshd"
-@@ -131,9 +132,12 @@ initialize_server_options(ServerOptions *options)
- 	options->kerberos_ticket_cleanup = -1;
- 	options->kerberos_get_afs_token = -1;
- 	options->gss_authentication=-1;
-+	options->gss_keyex = -1;
- 	options->gss_cleanup_creds = -1;
- 	options->gss_deleg_creds = -1;
- 	options->gss_strict_acceptor = -1;
-+	options->gss_store_rekey = -1;
-+	options->gss_kex_algorithms = NULL;
- 	options->password_authentication = -1;
- 	options->kbd_interactive_authentication = -1;
- 	options->permit_empty_passwd = -1;
-@@ -372,12 +376,20 @@ fill_default_server_options(ServerOptions *options)
- 		options->kerberos_get_afs_token = 0;
- 	if (options->gss_authentication == -1)
- 		options->gss_authentication = 0;
-+	if (options->gss_keyex == -1)
-+		options->gss_keyex = 0;
- 	if (options->gss_cleanup_creds == -1)
- 		options->gss_cleanup_creds = 1;
- 	if (options->gss_deleg_creds == -1)
- 		options->gss_deleg_creds = 1;
- 	if (options->gss_strict_acceptor == -1)
- 		options->gss_strict_acceptor = 1;
-+	if (options->gss_store_rekey == -1)
-+		options->gss_store_rekey = 0;
+ #define SSHD_CONFIG_BLOB_VERSION	1
+ 
+@@ -337,6 +338,10 @@ fill_default_server_options(ServerOptions *options)
+ 		options->log_facility = SYSLOG_FACILITY_AUTH;
+ 	if (options->log_level == SYSLOG_LEVEL_NOT_SET)
+ 		options->log_level = SYSLOG_LEVEL_INFO;
 +#ifdef GSSAPI
 +	if (options->gss_kex_algorithms == NULL)
 +		options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX);
 +#endif
- 	if (options->password_authentication == -1)
- 		options->password_authentication = 1;
- 	if (options->kbd_interactive_authentication == -1)
-@@ -565,6 +577,7 @@ typedef enum {
- 	sPerSourcePenalties, sPerSourcePenaltyExemptList,
- 	sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
- 	sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssStrictAcceptor,
-+	sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
- 	sAcceptEnv, sSetEnv, sPermitTunnel,
- 	sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
- 	sUsePrivilegeSeparation, sAllowAgentForwarding,
-@@ -650,14 +663,24 @@ static struct {
- #ifdef GSSAPI
- 	{ "gssapiauthentication", sGssAuthentication, SSHCFG_ALL },
- 	{ "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL },
-+	{ "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL },
- 	{ "gssapidelegatecredentials", sGssDelegateCreds, SSHCFG_GLOBAL },
- 	{ "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL },
-+	{ "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL },
-+	{ "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
-+	{ "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
- #else
- 	{ "gssapiauthentication", sUnsupported, SSHCFG_ALL },
- 	{ "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
-+	{ "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapidelegatecredentials", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL },
-+	{ "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL },
-+	{ "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
-+	{ "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
- #endif
-+	{ "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
-+	{ "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
- 	{ "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL },
- 	{ "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL },
- 	{ "challengeresponseauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, /* alias */
-@@ -1650,6 +1673,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ 	if (options->permit_user_env == -1) {
+ 		options->permit_user_env = 0;
+ 		options->permit_user_env_allowlist = NULL;
+@@ -1437,6 +1442,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
  		intptr = &options->gss_authentication;
  		goto parse_flag;
  
-+	case sGssKeyEx:
++	case sGSSAPIKeyExchange:
 +		intptr = &options->gss_keyex;
 +		goto parse_flag;
 +
- 	case sGssCleanupCreds:
+ 	case sGSSAPICleanupCredentials:
  		intptr = &options->gss_cleanup_creds;
  		goto parse_flag;
-@@ -1662,6 +1689,22 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+@@ -1448,6 +1457,22 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ 	case sGSSAPIStrictAcceptorCheck:
  		intptr = &options->gss_strict_acceptor;
  		goto parse_flag;
- 
-+	case sGssStoreRekey:
++
++	case sGSSAPIStoreCredentialsOnRekey:
 +		intptr = &options->gss_store_rekey;
 +		goto parse_flag;
 +
-+	case sGssKexAlgorithms:
++	case sGSSAPIKexAlgorithms:
 +		arg = argv_next(&ac, &av);
 +		if (!arg || *arg == '\0')
 +			fatal("%.200s line %d: Missing argument.",
@@ -3497,43 +3456,53 @@ index 668259a20..de3fa9481 100644
 +		if (*activep && options->gss_kex_algorithms == NULL)
 +			options->gss_kex_algorithms = xstrdup(arg);
 +		break;
-+
+ #endif /* GSSAPI */
+ 
  	case sPasswordAuthentication:
- 		intptr = &options->password_authentication;
- 		goto parse_flag;
-@@ -3291,7 +3334,10 @@ dump_config(ServerOptions *o)
- 	dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
- 	dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds);
- 	dump_cfg_fmtint(sGssDelegateCreds, o->gss_deleg_creds);
-+	dump_cfg_fmtint(sGssKeyEx, o->gss_keyex);
- 	dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor);
-+	dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey);
-+	dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms);
+@@ -4218,7 +4243,10 @@ dump_config(ServerOptions *o)
+ 	dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
+ 	dump_cfg_fmtint(sGSSAPICleanupCredentials, o->gss_cleanup_creds);
+ 	dump_cfg_fmtint(sGSSAPIDelegateCredentials, o->gss_deleg_creds);
++	dump_cfg_fmtint(sGSSAPIKeyExchange, o->gss_keyex);
+ 	dump_cfg_fmtint(sGSSAPIStrictAcceptorCheck, o->gss_strict_acceptor);
++	dump_cfg_fmtint(sGSSAPIStoreCredentialsOnRekey, o->gss_store_rekey);
++	dump_cfg_string(sGSSAPIKexAlgorithms, o->gss_kex_algorithms);
  #endif
  	dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
  	dump_cfg_fmtint(sKbdInteractiveAuthentication,
 diff --git a/servconf.h b/servconf.h
-index b06db09e1..76641d580 100644
+index a2345e88a..ddb0f130a 100644
 --- a/servconf.h
 +++ b/servconf.h
-@@ -151,9 +151,12 @@ typedef struct {
- 	int     kerberos_get_afs_token;		/* If true, try to get AFS token if
- 						 * authenticated with Kerberos. */
- 	int     gss_authentication;	/* If true, permit GSSAPI authentication */
-+	int     gss_keyex;		/* If true, permit GSSAPI key exchange */
- 	int     gss_cleanup_creds;	/* If true, destroy cred cache on logout */
- 	int     gss_deleg_creds;	/* If true, accept delegated GSS credentials */
- 	int     gss_strict_acceptor;	/* If true, restrict the GSSAPI acceptor name */
-+	int 	gss_store_rekey;
-+	char   *gss_kex_algorithms;	/* GSSAPI kex methods to be offered by client. */
- 	int     password_authentication;	/* If true, permit password
- 						 * authentication. */
- 	int     kbd_interactive_authentication;	/* If true, permit */
+@@ -316,14 +316,21 @@ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOB
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_INTFLAG(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+ SSHCONF_INTFLAG(gss_cleanup_creds, GSSAPICleanupCredentials, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
++SSHCONF_ALIAS(GSSAPICleanupCreds, GSSAPICleanupCredentials, SSHCFG_GLOBAL) \
+ SSHCONF_INTFLAG(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+-SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE)
++SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
++SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE)
+ #else /* GSSAPI */
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
+ SSHCONF_UNSUPPORTED_INT(gss_cleanup_creds, GSSAPICleanupCredentials, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL) \
+-SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL)
++SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL)
+ #endif /* GSSAPI */
+ 
+ #define SSHD_CONFIG_ENTRIES \
 diff --git a/session.c b/session.c
-index 93de35d7c..f14d5fab8 100644
+index fc9c9d6f8..7b76f1f71 100644
 --- a/session.c
 +++ b/session.c
-@@ -2638,13 +2638,19 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
+@@ -2642,13 +2642,19 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
  
  #ifdef KRB5
  	if (options.kerberos_ticket_cleanup &&
@@ -3684,7 +3653,7 @@ index 7b14e74a8..8ec451926 100644
  
  #endif /* _SSH_GSS_H */
 diff --git a/ssh.1 b/ssh.1
-index c2c8f50f1..b5d0a1d09 100644
+index 28eaeac2f..3aaf3fb16 100644
 --- a/ssh.1
 +++ b/ssh.1
 @@ -543,9 +543,15 @@ For full details of the options listed below, and their possible values, see
@@ -3701,9 +3670,9 @@ index c2c8f50f1..b5d0a1d09 100644
 +.It GSSAPIServerIdentity
 +.It GSSAPITrustDns
  .It HashKnownHosts
- .It Host
  .It HostKeyAlgorithms
-@@ -640,6 +646,8 @@ flag),
+ .It HostKeyAlias
+@@ -641,6 +647,8 @@ flag),
  (supported message integrity codes),
  .Ar kex
  (key exchange algorithms),
@@ -3713,10 +3682,10 @@ index c2c8f50f1..b5d0a1d09 100644
  (key types),
  .Ar key-ca-sign
 diff --git a/ssh.c b/ssh.c
-index 531f28eb2..bc3be8f8e 100644
+index 7c644c23c..07bb1e38f 100644
 --- a/ssh.c
 +++ b/ssh.c
-@@ -805,6 +805,9 @@ main(int ac, char **av)
+@@ -786,6 +786,9 @@ main(int ac, char **av)
  			else if (strcmp(optarg, "kex") == 0 ||
  			    strcasecmp(optarg, "KexAlgorithms") == 0)
  				cp = kex_alg_list('\n');
@@ -3726,7 +3695,7 @@ index 531f28eb2..bc3be8f8e 100644
  			else if (strcmp(optarg, "key") == 0)
  				cp = sshkey_alg_list(0, 0, 0, '\n');
  			else if (strcmp(optarg, "key-cert") == 0)
-@@ -835,8 +837,8 @@ main(int ac, char **av)
+@@ -816,8 +819,8 @@ main(int ac, char **av)
  			} else if (strcmp(optarg, "help") == 0) {
  				cp = xstrdup(
  				    "cipher\ncipher-auth\ncompression\nkex\n"
@@ -3751,10 +3720,10 @@ index d9324c957..ca7c5853b 100644
  #   CheckHostIP no
  #   AddressFamily any
 diff --git a/ssh_config.5 b/ssh_config.5
-index b459b0449..9e5163774 100644
+index 4ac0d3d45..0b5b8beab 100644
 --- a/ssh_config.5
 +++ b/ssh_config.5
-@@ -976,10 +976,71 @@ The default is
+@@ -1013,10 +1013,71 @@ The default is
  Specifies whether user authentication based on GSSAPI is allowed.
  The default is
  .Cm no .
@@ -3827,10 +3796,10 @@ index b459b0449..9e5163774 100644
  Indicates that
  .Xr ssh 1
 diff --git a/sshconnect2.c b/sshconnect2.c
-index 478a9a52f..31bbc0ed8 100644
+index d1555ee97..e1d4388ad 100644
 --- a/sshconnect2.c
 +++ b/sshconnect2.c
-@@ -223,6 +223,11 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+@@ -223,6 +223,11 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
  	char *all_key, *hkalgs = NULL;
  	int r, use_known_hosts_order = 0;
  
@@ -3839,10 +3808,10 @@ index 478a9a52f..31bbc0ed8 100644
 +	char *gss_host = NULL;
 +#endif
 +
- 	xxx_host = host;
- 	xxx_hostaddr = hostaddr;
- 	xxx_conn_info = cinfo;
-@@ -256,6 +261,42 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+ 	xxx_host = xstrdup(host);
+ 	xxx_hostaddr = *hostaddr;
+ 	xxx_conn_info = ssh_conn_info_dup(cinfo);
+@@ -258,6 +263,42 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
  	    compression_alg_list(options.compression),
  	    hkalgs ? hkalgs : options.hostkeyalgorithms);
  
@@ -3885,7 +3854,7 @@ index 478a9a52f..31bbc0ed8 100644
  	free(hkalgs);
  
  	/* start key exchange */
-@@ -272,15 +313,45 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+@@ -274,15 +315,45 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
  # ifdef OPENSSL_HAS_ECC
  	ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client;
  # endif
@@ -3932,7 +3901,7 @@ index 478a9a52f..31bbc0ed8 100644
  #ifdef DEBUG_KEXDH
  	/* send 1st encrypted/maced/compressed message */
  	if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 ||
-@@ -370,6 +441,7 @@ static int input_gssapi_response(int type, uint32_t, struct ssh *);
+@@ -372,6 +443,7 @@ static int input_gssapi_response(int type, uint32_t, struct ssh *);
  static int input_gssapi_token(int type, uint32_t, struct ssh *);
  static int input_gssapi_error(int, uint32_t, struct ssh *);
  static int input_gssapi_errtok(int, uint32_t, struct ssh *);
@@ -3940,7 +3909,7 @@ index 478a9a52f..31bbc0ed8 100644
  #endif
  
  void	userauth(struct ssh *, char *);
-@@ -386,6 +458,11 @@ static char *authmethods_get(void);
+@@ -388,6 +460,11 @@ static char *authmethods_get(void);
  
  Authmethod authmethods[] = {
  #ifdef GSSAPI
@@ -3952,7 +3921,7 @@ index 478a9a52f..31bbc0ed8 100644
  	{"gssapi-with-mic",
  		userauth_gssapi,
  		userauth_gssapi_cleanup,
-@@ -760,12 +837,32 @@ userauth_gssapi(struct ssh *ssh)
+@@ -762,12 +839,32 @@ userauth_gssapi(struct ssh *ssh)
  	OM_uint32 min;
  	int r, ok = 0;
  	gss_OID mech = NULL;
@@ -3986,7 +3955,7 @@ index 478a9a52f..31bbc0ed8 100644
  
  	/* Check to see whether the mechanism is usable before we offer it */
  	while (authctxt->mech_tried < authctxt->gss_supported_mechs->count &&
-@@ -774,13 +871,15 @@ userauth_gssapi(struct ssh *ssh)
+@@ -776,13 +873,15 @@ userauth_gssapi(struct ssh *ssh)
  		    elements[authctxt->mech_tried];
  		/* My DER encoding requires length<128 */
  		if (mech->length < 128 && ssh_gssapi_check_mechanism(&gssctxt,
@@ -4003,7 +3972,7 @@ index 478a9a52f..31bbc0ed8 100644
  	if (!ok || mech == NULL)
  		return 0;
  
-@@ -1014,6 +1113,55 @@ input_gssapi_error(int type, uint32_t plen, struct ssh *ssh)
+@@ -1016,6 +1115,55 @@ input_gssapi_error(int type, uint32_t plen, struct ssh *ssh)
  	free(lang);
  	return r;
  }
@@ -4060,10 +4029,10 @@ index 478a9a52f..31bbc0ed8 100644
  
  static int
 diff --git a/sshd-auth.c b/sshd-auth.c
-index 73acb0dbe..885cd8281 100644
+index 99359c718..6a5cd0c52 100644
 --- a/sshd-auth.c
 +++ b/sshd-auth.c
-@@ -691,7 +691,7 @@ main(int ac, char **av)
+@@ -665,7 +665,7 @@ main(int ac, char **av)
  			break;
  		}
  	}
@@ -4072,7 +4041,7 @@ index 73acb0dbe..885cd8281 100644
  		fatal("internal error: received no hostkeys");
  
  	/* Ensure that umask disallows at least group and world write */
-@@ -811,6 +811,48 @@ do_ssh2_kex(struct ssh *ssh)
+@@ -785,6 +785,48 @@ do_ssh2_kex(struct ssh *ssh)
  
  	free(hkalgs);
  
@@ -4121,7 +4090,7 @@ index 73acb0dbe..885cd8281 100644
  	if ((r = kex_exchange_identification(ssh, -1,
  	    options.version_addendum)) != 0)
  		sshpkt_fatal(ssh, r, "banner exchange");
-@@ -836,6 +878,17 @@ do_ssh2_kex(struct ssh *ssh)
+@@ -810,6 +852,17 @@ do_ssh2_kex(struct ssh *ssh)
  # ifdef OPENSSL_HAS_ECC
  	kex->kex[KEX_ECDH_SHA2] = kex_gen_server;
  # endif /* OPENSSL_HAS_ECC */
@@ -4140,10 +4109,10 @@ index 73acb0dbe..885cd8281 100644
  	kex->kex[KEX_C25519_SHA256] = kex_gen_server;
  	kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
 diff --git a/sshd-session.c b/sshd-session.c
-index 6976d6872..f847fc0a9 100644
+index 8c6652152..352237474 100644
 --- a/sshd-session.c
 +++ b/sshd-session.c
-@@ -564,8 +564,8 @@ notify_hostkeys(struct ssh *ssh)
+@@ -565,8 +565,8 @@ notify_hostkeys(struct ssh *ssh)
  	}
  	debug3_f("sent %u hostkeys", nkeys);
  	if (nkeys == 0)
@@ -4154,7 +4123,7 @@ index 6976d6872..f847fc0a9 100644
  		sshpkt_fatal(ssh, r, "%s: send", __func__);
  	sshbuf_free(buf);
  }
-@@ -1107,8 +1107,9 @@ main(int ac, char **av)
+@@ -1108,8 +1108,9 @@ main(int ac, char **av)
  			break;
  		}
  	}
@@ -4167,10 +4136,10 @@ index 6976d6872..f847fc0a9 100644
  	/* Ensure that umask disallows at least group and world write */
  	new_umask = umask(0077) | 0022;
 diff --git a/sshd.c b/sshd.c
-index 74d25fc73..552435817 100644
+index 7d1466d97..f66e1f86f 100644
 --- a/sshd.c
 +++ b/sshd.c
-@@ -1667,7 +1667,8 @@ main(int ac, char **av)
+@@ -1669,7 +1669,8 @@ main(int ac, char **av)
  		free(fp);
  	}
  	accumulate_host_timing_secret(cfg, NULL);
@@ -4194,10 +4163,10 @@ index 48af6321b..8db9f0fb1 100644
  # Set this to 'yes' to enable PAM authentication, account processing,
  # and session processing. If this is enabled, PAM authentication will
 diff --git a/sshd_config.5 b/sshd_config.5
-index ca425a8da..5928b6cd2 100644
+index 3896d21b0..b719fc915 100644
 --- a/sshd_config.5
 +++ b/sshd_config.5
-@@ -752,9 +752,19 @@ on logout.
+@@ -759,9 +759,19 @@ on logout.
  The default is
  .Cm yes .
  .It Cm GSSAPIDelegateCredentials
@@ -4219,10 +4188,10 @@ index ca425a8da..5928b6cd2 100644
  .It Cm GSSAPIStrictAcceptorCheck
  Determines whether to be strict about the identity of the GSSAPI acceptor
  a client authenticates against.
-@@ -769,6 +779,35 @@ machine's default store.
- This facility is provided to assist with operation on multi homed machines.
+@@ -777,6 +787,35 @@ This facility is provided to assist with operation on multi homed machines.
  The default is
  .Cm yes .
+ This option may not be effective in Windows Active Directory environments.
 +.It Cm GSSAPIStoreCredentialsOnRekey
 +Controls whether the user's GSSAPI credentials should be updated following a
 +successful connection rekeying. This option can be used to accepted renewed
@@ -4256,10 +4225,10 @@ index ca425a8da..5928b6cd2 100644
  Specifies the signature algorithms that will be accepted for hostbased
  authentication as a list of comma-separated patterns.
 diff --git a/sshkey.c b/sshkey.c
-index 59d14531c..ccc35dbf5 100644
+index 7f389daa9..6bb8feaa9 100644
 --- a/sshkey.c
 +++ b/sshkey.c
-@@ -115,6 +115,75 @@ extern const struct sshkey_impl sshkey_rsa_sha512_impl;
+@@ -119,6 +119,75 @@ extern const struct sshkey_impl sshkey_rsa_sha512_impl;
  extern const struct sshkey_impl sshkey_rsa_sha512_cert_impl;
  #endif /* WITH_OPENSSL */
  
@@ -4335,7 +4304,7 @@ index 59d14531c..ccc35dbf5 100644
  const struct sshkey_impl * const keyimpls[] = {
  	&sshkey_ed25519_impl,
  	&sshkey_ed25519_cert_impl,
-@@ -146,6 +215,7 @@ const struct sshkey_impl * const keyimpls[] = {
+@@ -154,6 +223,7 @@ const struct sshkey_impl * const keyimpls[] = {
  	&sshkey_rsa_sha512_impl,
  	&sshkey_rsa_sha512_cert_impl,
  #endif /* WITH_OPENSSL */
@@ -4343,7 +4312,7 @@ index 59d14531c..ccc35dbf5 100644
  	NULL
  };
  
-@@ -327,7 +397,7 @@ sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep)
+@@ -335,7 +405,7 @@ sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep)
  
  	for (i = 0; keyimpls[i] != NULL; i++) {
  		impl = keyimpls[i];
@@ -4353,17 +4322,17 @@ index 59d14531c..ccc35dbf5 100644
  		if (!include_sigonly && impl->sigonly)
  			continue;
 diff --git a/sshkey.h b/sshkey.h
-index a9cdfcd19..9eced5d8a 100644
+index 9ad5583a2..58cb58981 100644
 --- a/sshkey.h
 +++ b/sshkey.h
-@@ -67,6 +67,7 @@ enum sshkey_types {
- 	KEY_ECDSA_SK_CERT,
- 	KEY_ED25519_SK,
+@@ -69,6 +69,7 @@ enum sshkey_types {
  	KEY_ED25519_SK_CERT,
+ 	KEY_MLDSA44_ED25519,
+ 	KEY_MLDSA44_ED25519_CERT,
 +	KEY_NULL,
  	KEY_UNSPEC
  };
  
 -- 
-2.53.0
+2.55.0
 

diff --git a/0014-openssh-6.6p1-force_krb.patch b/0014-openssh-6.6p1-force_krb.patch
index 36b9f6c..d0661df 100644
--- a/0014-openssh-6.6p1-force_krb.patch
+++ b/0014-openssh-6.6p1-force_krb.patch
@@ -1,4 +1,4 @@
-From a0339453b9b0ac515752d0c60ffd9bcb46563e71 Mon Sep 17 00:00:00 2001
+From 1dea30d6fd38833e2a6b99d04ee551949082156b Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 14/54] openssh-6.6p1-force_krb
@@ -218,7 +218,7 @@ index be401fe0e..c26174928 100644
  /* This writes out any forwarded credentials from the structure populated
   * during userauth. Called after we have setuid to the user */
 diff --git a/session.c b/session.c
-index f14d5fab8..610a2cc6f 100644
+index 7b76f1f71..b4db64ea4 100644
 --- a/session.c
 +++ b/session.c
 @@ -643,6 +643,29 @@ do_exec(struct ssh *ssh, Session *s, const char *command)
@@ -292,5 +292,5 @@ index 0226a8303..e2d8ff003 100644
  This directory is the default location for all user-specific configuration
  and authentication information.
 -- 
-2.53.0
+2.55.0
 

diff --git a/0015-openssh-7.7p1-gssapi-new-unique.patch b/0015-openssh-7.7p1-gssapi-new-unique.patch
index f697b7b..118faac 100644
--- a/0015-openssh-7.7p1-gssapi-new-unique.patch
+++ b/0015-openssh-7.7p1-gssapi-new-unique.patch
@@ -1,22 +1,20 @@
-From ea30e158dcece39525beb0bc16eb4a26d8eb801f Mon Sep 17 00:00:00 2001
+From e0b2b31b38e6e3d1ad810782d3777def81a87a86 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 15/54] openssh-7.7p1-gssapi-new-unique
 
-# Improve ccache handling in openssh (#991186, #1199363, #1566494)
-# https://bugzilla.mindrot.org/show_bug.cgi?id=2775
 ---
  auth-krb5.c     | 262 ++++++++++++++++++++++++++++++++++++++++++------
  auth.h          |   3 +-
  gss-serv-krb5.c |  42 +++-----
  gss-serv.c      |  12 +--
- servconf.c      |  12 ++-
+ servconf.c      |   5 +
  servconf.h      |   2 +
  session.c       |   5 +-
  ssh-gss.h       |   4 +-
  sshd-session.c  |   2 +-
  sshd_config.5   |   8 ++
- 10 files changed, 280 insertions(+), 72 deletions(-)
+ 10 files changed, 274 insertions(+), 71 deletions(-)
 
 diff --git a/auth-krb5.c b/auth-krb5.c
 index 3c6dc0622..0de1aaf8d 100644
@@ -341,7 +339,7 @@ index 3c6dc0622..0de1aaf8d 100644
  #endif /* !HEIMDAL */
  #endif /* KRB5 */
 diff --git a/auth.h b/auth.h
-index 32ee3cbf4..9e55ff8ad 100644
+index efe819323..2a19e2b34 100644
 --- a/auth.h
 +++ b/auth.h
 @@ -85,6 +85,7 @@ struct Authctxt {
@@ -352,7 +350,7 @@ index 32ee3cbf4..9e55ff8ad 100644
  #endif
  	struct sshbuf	*loginmsg;
  
-@@ -243,7 +244,7 @@ FILE	*auth_openprincipals(const char *, struct passwd *, int);
+@@ -244,7 +245,7 @@ FILE	*auth_openprincipals(const char *, struct passwd *, int);
  int	 sys_auth_passwd(struct ssh *, const char *);
  
  #if defined(KRB5) && !defined(HEIMDAL)
@@ -516,83 +514,49 @@ index 8b4fa9cfe..6eb7d2163 100644
  
  	ok = mm_ssh_gssapi_update_creds(&gssapi_client.store);
 diff --git a/servconf.c b/servconf.c
-index de3fa9481..10b43d56a 100644
+index 6700f5d51..2d11e1d00 100644
 --- a/servconf.c
 +++ b/servconf.c
-@@ -131,6 +131,7 @@ initialize_server_options(ServerOptions *options)
- 	options->kerberos_or_local_passwd = -1;
- 	options->kerberos_ticket_cleanup = -1;
- 	options->kerberos_get_afs_token = -1;
-+	options->kerberos_unique_ccache = -1;
- 	options->gss_authentication=-1;
- 	options->gss_keyex = -1;
- 	options->gss_cleanup_creds = -1;
-@@ -374,6 +375,8 @@ fill_default_server_options(ServerOptions *options)
- 		options->kerberos_ticket_cleanup = 1;
- 	if (options->kerberos_get_afs_token == -1)
- 		options->kerberos_get_afs_token = 0;
-+	if (options->kerberos_unique_ccache == -1)
-+		options->kerberos_unique_ccache = 0;
- 	if (options->gss_authentication == -1)
- 		options->gss_authentication = 0;
- 	if (options->gss_keyex == -1)
-@@ -561,7 +564,7 @@ typedef enum {
- 	sPort, sHostKeyFile, sLoginGraceTime,
- 	sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose,
- 	sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
--	sKerberosGetAFSToken, sPasswordAuthentication,
-+	sKerberosGetAFSToken, sKerberosUniqueCCache, sPasswordAuthentication,
- 	sKbdInteractiveAuthentication, sListenAddress, sAddressFamily,
- 	sPrintMotd, sPrintLastLog, sIgnoreRhosts,
- 	sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
-@@ -652,11 +655,13 @@ static struct {
- #else
- 	{ "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL },
- #endif
-+	{ "kerberosuniqueccache", sKerberosUniqueCCache, SSHCFG_GLOBAL },
- #else
- 	{ "kerberosauthentication", sUnsupported, SSHCFG_ALL },
- 	{ "kerberosorlocalpasswd", sUnsupported, SSHCFG_GLOBAL },
- 	{ "kerberosticketcleanup", sUnsupported, SSHCFG_GLOBAL },
- 	{ "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL },
-+	{ "kerberosuniqueccache", sUnsupported, SSHCFG_GLOBAL },
- #endif
- 	{ "kerberostgtpassing", sUnsupported, SSHCFG_GLOBAL },
- 	{ "afstokenpassing", sUnsupported, SSHCFG_GLOBAL },
-@@ -1669,6 +1674,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
- 		intptr = &options->kerberos_get_afs_token;
- 		goto parse_flag;
+@@ -1437,6 +1437,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ #endif /* USE_AFS */
+ #endif /* KRB5 */
  
 +	case sKerberosUniqueCCache:
 +		intptr = &options->kerberos_unique_ccache;
 +		goto parse_flag;
 +
- 	case sGssAuthentication:
+ #ifdef GSSAPI
+ 	case sGSSAPIAuthentication:
  		intptr = &options->gss_authentication;
- 		goto parse_flag;
-@@ -3329,6 +3338,7 @@ dump_config(ServerOptions *o)
+@@ -4238,6 +4242,7 @@ dump_config(ServerOptions *o)
  # ifdef USE_AFS
  	dump_cfg_fmtint(sKerberosGetAFSToken, o->kerberos_get_afs_token);
  # endif
 +	dump_cfg_fmtint(sKerberosUniqueCCache, o->kerberos_unique_ccache);
  #endif
  #ifdef GSSAPI
- 	dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+ 	dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
 diff --git a/servconf.h b/servconf.h
-index 76641d580..bf589911a 100644
+index ddb0f130a..6ca89b24e 100644
 --- a/servconf.h
 +++ b/servconf.h
-@@ -150,6 +150,8 @@ typedef struct {
- 						 * file on logout. */
- 	int     kerberos_get_afs_token;		/* If true, try to get AFS token if
- 						 * authenticated with Kerberos. */
-+	int     kerberos_unique_ccache;		/* If true, the acquired ticket will
-+						 * be stored in per-session ccache */
- 	int     gss_authentication;	/* If true, permit GSSAPI authentication */
- 	int     gss_keyex;		/* If true, permit GSSAPI key exchange */
- 	int     gss_cleanup_creds;	/* If true, destroy cred cache on logout */
+@@ -303,12 +303,14 @@ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOB
+ SSHCONF_INTFLAG(kerberos_authentication, KerberosAuthentication, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+ SSHCONF_INTFLAG(kerberos_or_local_passwd, KerberosOrLocalPasswd, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(kerberos_ticket_cleanup, KerberosTicketCleanup, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(kerberos_unique_ccache, KerberosUniqueCCache, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHD_CONFIG_KRB5_AFS
+ #else /* KRB5 */
+ #define SSHD_CONFIG_ENTRIES_KRB5 \
+ SSHCONF_UNSUPPORTED_INT(kerberos_authentication, KerberosAuthentication, SSHCFG_ALL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_or_local_passwd, KerberosOrLocalPasswd, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_ticket_cleanup, KerberosTicketCleanup, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(kerberos_unique_ccache, KerberosUniqueCCache, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOBAL)
+ #endif /* KRB5 */
+ 
 diff --git a/session.c b/session.c
-index 610a2cc6f..517cab6b6 100644
+index b4db64ea4..c2cdd6c4f 100644
 --- a/session.c
 +++ b/session.c
 @@ -988,7 +988,8 @@ do_setup_env(struct ssh *ssh, Session *s, const char *shell)
@@ -637,10 +601,10 @@ index db34d77f4..a894e23c9 100644
  
  char *ssh_gssapi_server_mechanisms(void);
 diff --git a/sshd-session.c b/sshd-session.c
-index f847fc0a9..9d75a4555 100644
+index 352237474..3d2672cfd 100644
 --- a/sshd-session.c
 +++ b/sshd-session.c
-@@ -1295,7 +1295,7 @@ main(int ac, char **av)
+@@ -1296,7 +1296,7 @@ main(int ac, char **av)
  #ifdef GSSAPI
  	if (options.gss_authentication) {
  		temporarily_use_uid(authctxt->pw);
@@ -650,10 +614,10 @@ index f847fc0a9..9d75a4555 100644
  	}
  #endif
 diff --git a/sshd_config.5 b/sshd_config.5
-index 5928b6cd2..d0b0dfe36 100644
+index b719fc915..372af241b 100644
 --- a/sshd_config.5
 +++ b/sshd_config.5
-@@ -1057,6 +1057,14 @@ Specifies whether to automatically destroy the user's ticket cache
+@@ -1068,6 +1068,14 @@ Specifies whether to automatically destroy the user's ticket cache
  file on logout.
  The default is
  .Cm yes .
@@ -669,5 +633,5 @@ index 5928b6cd2..d0b0dfe36 100644
  Specifies the permitted KEX (Key Exchange) algorithms that the server will
  offer to clients.
 -- 
-2.53.0
+2.55.0
 

diff --git a/0016-openssh-7.2p2-k5login_directory.patch b/0016-openssh-7.2p2-k5login_directory.patch
index caa5b9d..e9126fe 100644
--- a/0016-openssh-7.2p2-k5login_directory.patch
+++ b/0016-openssh-7.2p2-k5login_directory.patch
@@ -1,4 +1,4 @@
-From f05d0adc8f15e01a3479d49645df26170be9d776 Mon Sep 17 00:00:00 2001
+From f4e2f83ba316fa67ab8052489f428c1cbe8cf3f4 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 16/54] openssh-7.2p2-k5login_directory
@@ -38,10 +38,10 @@ index 0de1aaf8d..148242cbf 100644
  #endif /* !HEIMDAL */
  #endif /* KRB5 */
 diff --git a/auth.h b/auth.h
-index 9e55ff8ad..815f821f9 100644
+index 2a19e2b34..9dd25f494 100644
 --- a/auth.h
 +++ b/auth.h
-@@ -245,6 +245,8 @@ int	 sys_auth_passwd(struct ssh *, const char *);
+@@ -246,6 +246,8 @@ int	 sys_auth_passwd(struct ssh *, const char *);
  
  #if defined(KRB5) && !defined(HEIMDAL)
  krb5_error_code ssh_krb5_cc_new_unique(krb5_context, krb5_ccache *, int *);
@@ -99,5 +99,5 @@ index e2d8ff003..fa33f5232 100644
  .It Pa ~/.ssh/
  This directory is the default location for all user-specific configuration
 -- 
-2.53.0
+2.55.0
 

diff --git a/0017-openssh-6.6p1-kuserok.patch b/0017-openssh-6.6p1-kuserok.patch
index 7e0ae52..344ec8a 100644
--- a/0017-openssh-6.6p1-kuserok.patch
+++ b/0017-openssh-6.6p1-kuserok.patch
@@ -1,17 +1,16 @@
-From bdb2b58e1a7f4d4a42a0aca1f8f3015b5edc86a1 Mon Sep 17 00:00:00 2001
+From 93b945d4af00e32012d3b401767e08bf2e5019a0 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 17/54] openssh-6.6p1-kuserok
 
-#https://bugzilla.mindrot.org/show_bug.cgi?id=1780
 ---
  auth-krb5.c     |  20 ++++++++-
  gss-serv-krb5.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++--
- servconf.c      |  13 +++++-
- servconf.h      |   1 +
+ servconf.c      |   5 +++
+ servconf.h      |   2 +
  sshd_config     |   1 +
  sshd_config.5   |   5 +++
- 6 files changed, 139 insertions(+), 7 deletions(-)
+ 6 files changed, 133 insertions(+), 6 deletions(-)
 
 diff --git a/auth-krb5.c b/auth-krb5.c
 index 148242cbf..287bdd35a 100644
@@ -190,51 +189,10 @@ index 7df31c6af..6f5826ce8 100644
  	if ((fp = fopen(file, "r")) == NULL) {
  		int saved_errno = errno;
 diff --git a/servconf.c b/servconf.c
-index 10b43d56a..751388bc0 100644
+index 2d11e1d00..fab3b4235 100644
 --- a/servconf.c
 +++ b/servconf.c
-@@ -139,6 +139,7 @@ initialize_server_options(ServerOptions *options)
- 	options->gss_strict_acceptor = -1;
- 	options->gss_store_rekey = -1;
- 	options->gss_kex_algorithms = NULL;
-+	options->use_kuserok = -1;
- 	options->password_authentication = -1;
- 	options->kbd_interactive_authentication = -1;
- 	options->permit_empty_passwd = -1;
-@@ -393,6 +394,8 @@ fill_default_server_options(ServerOptions *options)
- 	if (options->gss_kex_algorithms == NULL)
- 		options->gss_kex_algorithms = strdup(GSS_KEX_DEFAULT_KEX);
- #endif
-+	if (options->use_kuserok == -1)
-+		options->use_kuserok = 1;
- 	if (options->password_authentication == -1)
- 		options->password_authentication = 1;
- 	if (options->kbd_interactive_authentication == -1)
-@@ -564,7 +567,7 @@ typedef enum {
- 	sPort, sHostKeyFile, sLoginGraceTime,
- 	sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose,
- 	sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
--	sKerberosGetAFSToken, sKerberosUniqueCCache, sPasswordAuthentication,
-+	sKerberosGetAFSToken, sKerberosUniqueCCache, sKerberosUseKuserok, sPasswordAuthentication,
- 	sKbdInteractiveAuthentication, sListenAddress, sAddressFamily,
- 	sPrintMotd, sPrintLastLog, sIgnoreRhosts,
- 	sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
-@@ -656,12 +659,14 @@ static struct {
- 	{ "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL },
- #endif
- 	{ "kerberosuniqueccache", sKerberosUniqueCCache, SSHCFG_GLOBAL },
-+	{ "kerberosusekuserok", sKerberosUseKuserok, SSHCFG_ALL },
- #else
- 	{ "kerberosauthentication", sUnsupported, SSHCFG_ALL },
- 	{ "kerberosorlocalpasswd", sUnsupported, SSHCFG_GLOBAL },
- 	{ "kerberosticketcleanup", sUnsupported, SSHCFG_GLOBAL },
- 	{ "kerberosgetafstoken", sUnsupported, SSHCFG_GLOBAL },
- 	{ "kerberosuniqueccache", sUnsupported, SSHCFG_GLOBAL },
-+	{ "kerberosusekuserok", sUnsupported, SSHCFG_ALL },
- #endif
- 	{ "kerberostgtpassing", sUnsupported, SSHCFG_GLOBAL },
- 	{ "afstokenpassing", sUnsupported, SSHCFG_GLOBAL },
-@@ -2463,6 +2468,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+@@ -2230,6 +2230,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
  		}
  		break;
  
@@ -245,34 +203,34 @@ index 10b43d56a..751388bc0 100644
  	case sMatch:
  		if (cmdline)
  			fatal("Match directive not supported as a command-line "
-@@ -3030,6 +3039,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
- 	M_CP_INTOPT(client_alive_interval);
- 	M_CP_INTOPT(ip_qos_interactive);
- 	M_CP_INTOPT(ip_qos_bulk);
-+	M_CP_INTOPT(use_kuserok);
- 	M_CP_INTOPT(rekey_limit);
- 	M_CP_INTOPT(rekey_interval);
- 	M_CP_INTOPT(log_level);
-@@ -3339,6 +3349,7 @@ dump_config(ServerOptions *o)
+@@ -4243,6 +4247,7 @@ dump_config(ServerOptions *o)
  	dump_cfg_fmtint(sKerberosGetAFSToken, o->kerberos_get_afs_token);
  # endif
  	dump_cfg_fmtint(sKerberosUniqueCCache, o->kerberos_unique_ccache);
 +	dump_cfg_fmtint(sKerberosUseKuserok, o->use_kuserok);
  #endif
  #ifdef GSSAPI
- 	dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+ 	dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
 diff --git a/servconf.h b/servconf.h
-index bf589911a..9843f96c5 100644
+index 6ca89b24e..59e98604a 100644
 --- a/servconf.h
 +++ b/servconf.h
-@@ -152,6 +152,7 @@ typedef struct {
- 						 * authenticated with Kerberos. */
- 	int     kerberos_unique_ccache;		/* If true, the acquired ticket will
- 						 * be stored in per-session ccache */
-+	int	use_kuserok;
- 	int     gss_authentication;	/* If true, permit GSSAPI authentication */
- 	int     gss_keyex;		/* If true, permit GSSAPI key exchange */
- 	int     gss_cleanup_creds;	/* If true, destroy cred cache on logout */
+@@ -304,6 +304,7 @@ SSHCONF_INTFLAG(kerberos_authentication, KerberosAuthentication, SSHCFG_ALL, 0,
+ SSHCONF_INTFLAG(kerberos_or_local_passwd, KerberosOrLocalPasswd, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(kerberos_ticket_cleanup, KerberosTicketCleanup, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(kerberos_unique_ccache, KerberosUniqueCCache, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(use_kuserok, KerberosUseKuserok, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
+ SSHD_CONFIG_KRB5_AFS
+ #else /* KRB5 */
+ #define SSHD_CONFIG_ENTRIES_KRB5 \
+@@ -311,6 +312,7 @@ SSHCONF_UNSUPPORTED_INT(kerberos_authentication, KerberosAuthentication, SSHCFG_
+ SSHCONF_UNSUPPORTED_INT(kerberos_or_local_passwd, KerberosOrLocalPasswd, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_ticket_cleanup, KerberosTicketCleanup, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_unique_ccache, KerberosUniqueCCache, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(use_kuserok, KerberosUseKuserok, SSHCFG_ALL) \
+ SSHCONF_UNSUPPORTED_INT(kerberos_get_afs_token, KerberosGetAFSToken, SSHCFG_GLOBAL)
+ #endif /* KRB5 */
+ 
 diff --git a/sshd_config b/sshd_config
 index 8db9f0fb1..ea5a878e6 100644
 --- a/sshd_config
@@ -286,10 +244,10 @@ index 8db9f0fb1..ea5a878e6 100644
  # GSSAPI options
  #GSSAPIAuthentication no
 diff --git a/sshd_config.5 b/sshd_config.5
-index d0b0dfe36..1762874c4 100644
+index 372af241b..c3a4806e0 100644
 --- a/sshd_config.5
 +++ b/sshd_config.5
-@@ -1065,6 +1065,10 @@ The default value
+@@ -1076,6 +1076,10 @@ The default value
  .Cm no
  can lead to overwriting previous tickets by subseqent connections to the same
  user account.
@@ -300,7 +258,7 @@ index d0b0dfe36..1762874c4 100644
  .It Cm KexAlgorithms
  Specifies the permitted KEX (Key Exchange) algorithms that the server will
  offer to clients.
-@@ -1379,6 +1383,7 @@ Available keywords are
+@@ -1390,6 +1394,7 @@ Available keywords are
  .Cm IPQoS ,
  .Cm KbdInteractiveAuthentication ,
  .Cm KerberosAuthentication ,
@@ -309,5 +267,5 @@ index d0b0dfe36..1762874c4 100644
  .Cm MaxAuthTries ,
  .Cm MaxSessions ,
 -- 
-2.53.0
+2.55.0
 

diff --git a/0018-openssh-6.4p1-fromto-remote.patch b/0018-openssh-6.4p1-fromto-remote.patch
index 3bee6a3..90f0431 100644
--- a/0018-openssh-6.4p1-fromto-remote.patch
+++ b/0018-openssh-6.4p1-fromto-remote.patch
@@ -1,4 +1,4 @@
-From c095e2f48e279af65a9419f580ac9cae14453a93 Mon Sep 17 00:00:00 2001
+From a19debf855d417dc8c309ff93ed6a98c61418b66 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 18/54] openssh-6.4p1-fromto-remote
@@ -9,7 +9,7 @@ Subject: [PATCH 18/54] openssh-6.4p1-fromto-remote
  1 file changed, 4 insertions(+), 1 deletion(-)
 
 diff --git a/scp.c b/scp.c
-index 1faa9a555..d87d939a1 100644
+index 28ac7a29d..4821514ba 100644
 --- a/scp.c
 +++ b/scp.c
 @@ -1143,7 +1143,10 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
@@ -25,5 +25,5 @@ index 1faa9a555..d87d939a1 100644
  				addargs(&alist, "%s",
  				    remote_remote_args.list[j]);
 -- 
-2.53.0
+2.55.0
 

diff --git a/0019-openssh-6.6.1p1-log-in-chroot.patch b/0019-openssh-6.6.1p1-log-in-chroot.patch
index b4a46ab..864d920 100644
--- a/0019-openssh-6.6.1p1-log-in-chroot.patch
+++ b/0019-openssh-6.6.1p1-log-in-chroot.patch
@@ -1,9 +1,8 @@
-From f187210c18f933a3b239c49bd09efeabba4e4712 Mon Sep 17 00:00:00 2001
+From 1befa886920ae129c1fdae4bd0dcfa960c6196c4 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 19/54] openssh-6.6.1p1-log-in-chroot
 
-# log via monitor in chroots without /dev/log (#2681)
 ---
  log.c              | 11 +++++++++--
  log.h              |  1 +
@@ -58,10 +57,10 @@ index 8e8dfc23f..70048a8af 100644
  int      log_change_level(LogLevel);
  int      log_is_on_stderr(void);
 diff --git a/monitor.c b/monitor.c
-index 025c6abbe..5255083ab 100644
+index cddbc0a22..7e1c9061c 100644
 --- a/monitor.c
 +++ b/monitor.c
-@@ -2030,9 +2030,22 @@ monitor_init(void)
+@@ -2004,9 +2004,22 @@ monitor_init(void)
  }
  
  void
@@ -100,7 +99,7 @@ index 75a0d6181..f4721c472 100644
  struct Authctxt;
  void monitor_child_preauth(struct ssh *, struct monitor *);
 diff --git a/session.c b/session.c
-index 517cab6b6..0c26448d9 100644
+index c2cdd6c4f..572487b4a 100644
 --- a/session.c
 +++ b/session.c
 @@ -162,6 +162,7 @@ login_cap_t *lc;
@@ -154,12 +153,12 @@ index 517cab6b6..0c26448d9 100644
  	do_rc_files(ssh, s, shell);
  
  	/* restore SIGPIPE for child */
-@@ -1645,9 +1641,17 @@ do_child(struct ssh *ssh, Session *s, const char *command)
+@@ -1646,9 +1642,17 @@ do_child(struct ssh *ssh, Session *s, const char *command)
  #ifdef WITH_SELINUX
  		ssh_selinux_change_context("sftpd_t");
  #endif
--		exit(sftp_server_main(i, argv, s->pw));
-+		exit(sftp_server_main(i, argv, s->pw, have_dev_log));
+-		exit(sftp_server_main(sftp_argc, sftp_argv, s->pw));
++		exit(sftp_server_main(sftp_argc, sftp_argv, s->pw, have_dev_log));
  	}
  
 +	/*
@@ -185,10 +184,10 @@ index 2c70f89bc..bbb79f278 100644
 +	return (sftp_server_main(argc, argv, user_pw, 0));
  }
 diff --git a/sftp-server.c b/sftp-server.c
-index ebdb31d32..7a6e4fa88 100644
+index f87fc5825..eef9a96f8 100644
 --- a/sftp-server.c
 +++ b/sftp-server.c
-@@ -1893,7 +1893,7 @@ sftp_server_usage(void)
+@@ -1918,7 +1918,7 @@ sftp_server_usage(void)
  }
  
  int
@@ -197,7 +196,7 @@ index ebdb31d32..7a6e4fa88 100644
  {
  	int i, r, in, out, ch, skipargs = 0, log_stderr = 0;
  	ssize_t len, olen;
-@@ -1905,7 +1905,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
+@@ -1930,7 +1930,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
  	extern char *__progname;
  
  	__progname = ssh_get_progname(argv[0]);
@@ -206,7 +205,7 @@ index ebdb31d32..7a6e4fa88 100644
  
  	pw = pwcopy(user_pw);
  
-@@ -1978,7 +1978,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
+@@ -2003,7 +2003,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
  		}
  	}
  
@@ -227,7 +226,7 @@ index 2bde8bb7f..ddf1a3968 100644
 +int	sftp_server_main(int, char **, struct passwd *, int);
  void	sftp_server_cleanup_exit(int) __attribute__((noreturn));
 diff --git a/sshd-session.c b/sshd-session.c
-index 9d75a4555..23b80eb01 100644
+index 3d2672cfd..6f07c843a 100644
 --- a/sshd-session.c
 +++ b/sshd-session.c
 @@ -387,7 +387,7 @@ privsep_postauth(struct ssh *ssh, Authctxt *authctxt)
@@ -252,5 +251,5 @@ index 9d75a4555..23b80eb01 100644
  	/* Demote the private keys to public keys. */
  	demote_sensitive_data();
 -- 
-2.53.0
+2.55.0
 

diff --git a/0020-openssh-6.6.1p1-scp-non-existing-directory.patch b/0020-openssh-6.6.1p1-scp-non-existing-directory.patch
index 6e266f2..2af4dd7 100644
--- a/0020-openssh-6.6.1p1-scp-non-existing-directory.patch
+++ b/0020-openssh-6.6.1p1-scp-non-existing-directory.patch
@@ -1,4 +1,4 @@
-From 276729dc37d204e3da80455788deeac854c72b46 Mon Sep 17 00:00:00 2001
+From 2e7a638b1bbe7959f6e5b255c270fa50d2262ada Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 20/54] openssh-6.6.1p1-scp-non-existing-directory
@@ -9,7 +9,7 @@ Subject: [PATCH 20/54] openssh-6.6.1p1-scp-non-existing-directory
  1 file changed, 4 insertions(+)
 
 diff --git a/scp.c b/scp.c
-index d87d939a1..b5ad45c12 100644
+index 4821514ba..fca4393f3 100644
 --- a/scp.c
 +++ b/scp.c
 @@ -1871,6 +1871,10 @@ sink(int argc, char **argv, const char *src)
@@ -24,5 +24,5 @@ index d87d939a1..b5ad45c12 100644
  		mode |= S_IWUSR;
  		if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) == -1) {
 -- 
-2.53.0
+2.55.0
 

diff --git a/0021-openssh-6.6p1-GSSAPIEnablek5users.patch b/0021-openssh-6.6p1-GSSAPIEnablek5users.patch
index d62778b..ac6135a 100644
--- a/0021-openssh-6.6p1-GSSAPIEnablek5users.patch
+++ b/0021-openssh-6.6p1-GSSAPIEnablek5users.patch
@@ -1,14 +1,14 @@
-From cf6f839370b1f8a207de08b4b00c28ce6684d182 Mon Sep 17 00:00:00 2001
+From 5784381d6d842e88a5a0ea896a9c723227757ee3 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 21/54] openssh-6.6p1-GSSAPIEnablek5users
 
 ---
- gss-serv-krb5.c |  3 +--
- servconf.c      | 13 ++++++++++++-
- servconf.h      |  1 +
- sshd_config     |  1 +
- 4 files changed, 15 insertions(+), 3 deletions(-)
+ gss-serv-krb5.c | 3 +--
+ servconf.c      | 5 +++++
+ servconf.h      | 6 ++++--
+ sshd_config     | 1 +
+ 4 files changed, 11 insertions(+), 4 deletions(-)
 
 diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
 index 6f5826ce8..723509409 100644
@@ -32,90 +32,52 @@ index 6f5826ce8..723509409 100644
                                          k5login_exists);
  	}
 diff --git a/servconf.c b/servconf.c
-index 751388bc0..8de81fdc8 100644
+index fab3b4235..1bfe6a016 100644
 --- a/servconf.c
 +++ b/servconf.c
-@@ -140,6 +140,7 @@ initialize_server_options(ServerOptions *options)
- 	options->gss_store_rekey = -1;
- 	options->gss_kex_algorithms = NULL;
- 	options->use_kuserok = -1;
-+	options->enable_k5users = -1;
- 	options->password_authentication = -1;
- 	options->kbd_interactive_authentication = -1;
- 	options->permit_empty_passwd = -1;
-@@ -396,6 +397,8 @@ fill_default_server_options(ServerOptions *options)
- #endif
- 	if (options->use_kuserok == -1)
- 		options->use_kuserok = 1;
-+	if (options->enable_k5users == -1)
-+		options->enable_k5users = 0;
- 	if (options->password_authentication == -1)
- 		options->password_authentication = 1;
- 	if (options->kbd_interactive_authentication == -1)
-@@ -582,7 +585,7 @@ typedef enum {
- 	sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize,
- 	sPerSourcePenalties, sPerSourcePenaltyExemptList,
- 	sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
--	sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssStrictAcceptor,
-+	sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssEnablek5users, sGssStrictAcceptor,
- 	sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
- 	sAcceptEnv, sSetEnv, sPermitTunnel,
- 	sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
-@@ -679,6 +682,7 @@ static struct {
- 	{ "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL },
- 	{ "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
- 	{ "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
-+	{ "gssapienablek5users", sGssEnablek5users, SSHCFG_ALL },
- #else
- 	{ "gssapiauthentication", sUnsupported, SSHCFG_ALL },
- 	{ "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
-@@ -688,6 +692,7 @@ static struct {
- 	{ "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
-+	{ "gssapienablek5users", sUnsupported, SSHCFG_ALL },
- #endif
- 	{ "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
-@@ -2472,6 +2477,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+@@ -2234,6 +2234,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
  		intptr = &options->use_kuserok;
  		goto parse_flag;
  
-+	case sGssEnablek5users:
++	case sGSSAPIEnablek5users:
 +		intptr = &options->enable_k5users;
 +		goto parse_flag;
 +
  	case sMatch:
  		if (cmdline)
  			fatal("Match directive not supported as a command-line "
-@@ -3040,6 +3049,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
- 	M_CP_INTOPT(ip_qos_interactive);
- 	M_CP_INTOPT(ip_qos_bulk);
- 	M_CP_INTOPT(use_kuserok);
-+	M_CP_INTOPT(enable_k5users);
- 	M_CP_INTOPT(rekey_limit);
- 	M_CP_INTOPT(rekey_interval);
- 	M_CP_INTOPT(log_level);
-@@ -3350,6 +3360,7 @@ dump_config(ServerOptions *o)
+@@ -4248,6 +4252,7 @@ dump_config(ServerOptions *o)
  # endif
  	dump_cfg_fmtint(sKerberosUniqueCCache, o->kerberos_unique_ccache);
  	dump_cfg_fmtint(sKerberosUseKuserok, o->use_kuserok);
-+	dump_cfg_fmtint(sGssEnablek5users, o->enable_k5users);
++	dump_cfg_fmtint(sGSSAPIEnablek5users, o->enable_k5users);
  #endif
  #ifdef GSSAPI
- 	dump_cfg_fmtint(sGssAuthentication, o->gss_authentication);
+ 	dump_cfg_fmtint(sGSSAPIAuthentication, o->gss_authentication);
 diff --git a/servconf.h b/servconf.h
-index 9843f96c5..569100e47 100644
+index 59e98604a..7e809cfff 100644
 --- a/servconf.h
 +++ b/servconf.h
-@@ -153,6 +153,7 @@ typedef struct {
- 	int     kerberos_unique_ccache;		/* If true, the acquired ticket will
- 						 * be stored in per-session ccache */
- 	int	use_kuserok;
-+	int		enable_k5users;
- 	int     gss_authentication;	/* If true, permit GSSAPI authentication */
- 	int     gss_keyex;		/* If true, permit GSSAPI key exchange */
- 	int     gss_cleanup_creds;	/* If true, destroy cred cache on logout */
+@@ -325,7 +325,8 @@ SSHCONF_INTFLAG(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBAL, 1, SS
+ SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+-SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE)
++SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
++SSHCONF_INTFLAG(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH)
+ #else /* GSSAPI */
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
+@@ -334,7 +335,8 @@ SSHCONF_UNSUPPORTED_INT(gss_deleg_creds, GSSAPIDelegateCredentials, SSHCFG_GLOBA
+ SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL) \
+-SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL)
++SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL) \
++SSHCONF_UNSUPPORTED_INT(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL)
+ #endif /* GSSAPI */
+ 
+ #define SSHD_CONFIG_ENTRIES \
 diff --git a/sshd_config b/sshd_config
 index ea5a878e6..33713c886 100644
 --- a/sshd_config
@@ -129,5 +91,5 @@ index ea5a878e6..33713c886 100644
  # Set this to 'yes' to enable PAM authentication, account processing,
  # and session processing. If this is enabled, PAM authentication will
 -- 
-2.53.0
+2.55.0
 

diff --git a/0022-openssh-6.8p1-sshdT-output.patch b/0022-openssh-6.8p1-sshdT-output.patch
index bebadf3..108791a 100644
--- a/0022-openssh-6.8p1-sshdT-output.patch
+++ b/0022-openssh-6.8p1-sshdT-output.patch
@@ -1,4 +1,4 @@
-From 49bd8aa161e505e9c59e97246e8981be3fd92f63 Mon Sep 17 00:00:00 2001
+From a472781da7c8dc2ef699d87c0081870960c7bb06 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 22/54] openssh-6.8p1-sshdT-output
@@ -9,10 +9,10 @@ Subject: [PATCH 22/54] openssh-6.8p1-sshdT-output
  1 file changed, 1 insertion(+), 1 deletion(-)
 
 diff --git a/servconf.c b/servconf.c
-index 8de81fdc8..8045756fb 100644
+index 1bfe6a016..8c1e9c253 100644
 --- a/servconf.c
 +++ b/servconf.c
-@@ -3403,7 +3403,7 @@ dump_config(ServerOptions *o)
+@@ -4295,7 +4295,7 @@ dump_config(ServerOptions *o)
  	dump_cfg_string(sXAuthLocation, o->xauth_location);
  	dump_cfg_string(sCiphers, o->ciphers);
  	dump_cfg_string(sMacs, o->macs);
@@ -22,5 +22,5 @@ index 8de81fdc8..8045756fb 100644
  	dump_cfg_string(sChrootDirectory, o->chroot_directory);
  	dump_cfg_string(sTrustedUserCAKeys, o->trusted_user_ca_keys);
 -- 
-2.53.0
+2.55.0
 

diff --git a/0023-openssh-6.7p1-sftp-force-permission.patch b/0023-openssh-6.7p1-sftp-force-permission.patch
index 3d7f5d0..3a7d3cb 100644
--- a/0023-openssh-6.7p1-sftp-force-permission.patch
+++ b/0023-openssh-6.7p1-sftp-force-permission.patch
@@ -1,4 +1,4 @@
-From 3ea41ef9bf2da8814a1f579898a839b5150d7b6b Mon Sep 17 00:00:00 2001
+From e0455cb1f98914322afe00c601e7b8677253399c Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 23/54] openssh-6.7p1-sftp-force-permission
@@ -10,7 +10,7 @@ Subject: [PATCH 23/54] openssh-6.7p1-sftp-force-permission
  2 files changed, 29 insertions(+), 2 deletions(-)
 
 diff --git a/sftp-server.8 b/sftp-server.8
-index 5311bf929..5e6e3aa44 100644
+index d9060ab9a..cd5b035d6 100644
 --- a/sftp-server.8
 +++ b/sftp-server.8
 @@ -38,6 +38,7 @@
@@ -21,7 +21,7 @@ index 5311bf929..5e6e3aa44 100644
  .Ek
  .Nm
  .Fl Q Ar protocol_feature
-@@ -138,6 +139,12 @@ Sets an explicit
+@@ -149,6 +150,12 @@ Sets an explicit
  .Xr umask 2
  to be applied to newly-created files and directories, instead of the
  user's default mask.
@@ -35,7 +35,7 @@ index 5311bf929..5e6e3aa44 100644
  .Pp
  On some systems,
 diff --git a/sftp-server.c b/sftp-server.c
-index 7a6e4fa88..d18c4acb9 100644
+index eef9a96f8..567cf6e9a 100644
 --- a/sftp-server.c
 +++ b/sftp-server.c
 @@ -68,6 +68,10 @@ struct sshbuf *oqueue;
@@ -77,7 +77,7 @@ index 7a6e4fa88..d18c4acb9 100644
  	if (status != SSH2_FX_OK)
  		send_status(id, status);
  	free(name);
-@@ -1886,7 +1897,7 @@ sftp_server_usage(void)
+@@ -1911,7 +1922,7 @@ sftp_server_usage(void)
  	fprintf(stderr,
  	    "usage: %s [-ehR] [-d start_directory] [-f log_facility] "
  	    "[-l log_level]\n\t[-P denied_requests] "
@@ -86,7 +86,7 @@ index 7a6e4fa88..d18c4acb9 100644
  	    "       %s -Q protocol_feature\n",
  	    __progname, __progname);
  	exit(1);
-@@ -1910,7 +1921,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw, int reset_handle
+@@ -1935,7 +1946,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw, int reset_handle
  	pw = pwcopy(user_pw);
  
  	while (!skipargs && (ch = getopt(argc, argv,
@@ -95,7 +95,7 @@ index 7a6e4fa88..d18c4acb9 100644
  		switch (ch) {
  		case 'Q':
  			if (strcasecmp(optarg, "requests") != 0) {
-@@ -1972,6 +1983,15 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw, int reset_handle
+@@ -1997,6 +2008,15 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw, int reset_handle
  				fatal("Invalid umask \"%s\"", optarg);
  			(void)umask((mode_t)mask);
  			break;
@@ -112,5 +112,5 @@ index 7a6e4fa88..d18c4acb9 100644
  		default:
  			sftp_server_usage();
 -- 
-2.53.0
+2.55.0
 

diff --git a/0024-openssh-7.2p2-s390-closefrom.patch b/0024-openssh-7.2p2-s390-closefrom.patch
index bd3f4f3..e60cf60 100644
--- a/0024-openssh-7.2p2-s390-closefrom.patch
+++ b/0024-openssh-7.2p2-s390-closefrom.patch
@@ -1,4 +1,4 @@
-From 6f9059876d4323de33ca706da0e883d142757f46 Mon Sep 17 00:00:00 2001
+From d4f7c3339ad03a74e739c9d5e6129a6161c5a071 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 24/54] openssh-7.2p2-s390-closefrom
@@ -47,5 +47,5 @@ index 49a4f35ff..f61124585 100644
  	(void) closedir(dirp);
  	return;
 -- 
-2.53.0
+2.55.0
 

diff --git a/0025-openssh-7.5p1-sandbox.patch b/0025-openssh-7.5p1-sandbox.patch
index b5a24a4..1d5be7a 100644
--- a/0025-openssh-7.5p1-sandbox.patch
+++ b/0025-openssh-7.5p1-sandbox.patch
@@ -1,4 +1,4 @@
-From 164a1599ea7f147919f6c925ee17c21820b9e7ee Mon Sep 17 00:00:00 2001
+From 7c814435d1c439bcbfb916c4e9962be59641d663 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 25/54] openssh-7.5p1-sandbox
@@ -9,7 +9,7 @@ Subject: [PATCH 25/54] openssh-7.5p1-sandbox
  1 file changed, 21 insertions(+)
 
 diff --git a/sandbox-seccomp-filter.c b/sandbox-seccomp-filter.c
-index 7b2444930..e21048f3a 100644
+index bf753eef2..b65413c95 100644
 --- a/sandbox-seccomp-filter.c
 +++ b/sandbox-seccomp-filter.c
 @@ -305,6 +305,9 @@ static const struct sock_filter preauth_insns[] = {
@@ -55,5 +55,5 @@ index 7b2444930..e21048f3a 100644
  	SC_ALLOW(__NR_getuid),
  #endif
 -- 
-2.53.0
+2.55.0
 

diff --git a/0026-openssh-7.8p1-scp-ipv6.patch b/0026-openssh-7.8p1-scp-ipv6.patch
index 71c7386..d96db05 100644
--- a/0026-openssh-7.8p1-scp-ipv6.patch
+++ b/0026-openssh-7.8p1-scp-ipv6.patch
@@ -1,4 +1,4 @@
-From 3e86feb824f2f1a7efe4b6b7b35c145bef1d0dd4 Mon Sep 17 00:00:00 2001
+From dffc82fcf7c31924fc79eea20561c8c4cc670452 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 26/54] openssh-7.8p1-scp-ipv6
@@ -9,7 +9,7 @@ Subject: [PATCH 26/54] openssh-7.8p1-scp-ipv6
  1 file changed, 3 insertions(+), 1 deletion(-)
 
 diff --git a/scp.c b/scp.c
-index b5ad45c12..5618e28e7 100644
+index fca4393f3..ac43ec5f4 100644
 --- a/scp.c
 +++ b/scp.c
 @@ -1164,7 +1164,9 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
@@ -24,5 +24,5 @@ index b5ad45c12..5618e28e7 100644
  			    thost, targ);
  			if (do_local_cmd(&alist) != 0)
 -- 
-2.53.0
+2.55.0
 

diff --git a/0027-openssh-8.0p1-crypto-policies.patch b/0027-openssh-8.0p1-crypto-policies.patch
index 8a1b7d4..63c23b9 100644
--- a/0027-openssh-8.0p1-crypto-policies.patch
+++ b/0027-openssh-8.0p1-crypto-policies.patch
@@ -1,18 +1,18 @@
-From 0914766aed901780fb744b4ba06e500ad03cf428 Mon Sep 17 00:00:00 2001
+From 7a4fb1259e51621440fe0d0a27739ecf826b37bf Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 27/54] openssh-8.0p1-crypto-policies
 
 ---
- ssh_config.5  | 168 +++++++++++++++++++--------------------------
+ ssh_config.5  | 167 +++++++++++++++++++-------------------------
  sshd_config.5 | 186 ++++++++++++++++++--------------------------------
- 2 files changed, 140 insertions(+), 214 deletions(-)
+ 2 files changed, 139 insertions(+), 214 deletions(-)
 
 diff --git a/ssh_config.5 b/ssh_config.5
-index 9e5163774..306fa1d8a 100644
+index 0b5b8beab..0447c8c7e 100644
 --- a/ssh_config.5
 +++ b/ssh_config.5
-@@ -438,17 +438,13 @@ A single argument of
+@@ -468,17 +468,13 @@ A single argument of
  causes no CNAMEs to be considered for canonicalization.
  This is the default behaviour.
  .It Cm CASignatureAlgorithms
@@ -35,7 +35,7 @@ index 9e5163774..306fa1d8a 100644
  If the specified list begins with a
  .Sq +
  character, then the specified algorithms will be appended to the default set
-@@ -587,20 +583,25 @@ If the option is set to
+@@ -617,20 +613,25 @@ If the option is set to
  (the default),
  the check will not be executed.
  .It Cm Ciphers
@@ -65,7 +65,7 @@ index 9e5163774..306fa1d8a 100644
  .Pp
  The supported ciphers are:
  .Bd -literal -offset indent
-@@ -616,13 +617,6 @@ aes256-gcm@openssh.com
+@@ -646,13 +647,6 @@ aes256-gcm@openssh.com
  chacha20-poly1305@openssh.com
  .Ed
  .Pp
@@ -79,7 +79,7 @@ index 9e5163774..306fa1d8a 100644
  The list of available ciphers may also be obtained using
  .Qq ssh -Q cipher .
  .It Cm ClearAllForwardings
-@@ -1022,6 +1016,11 @@ command line will be passed untouched to the GSSAPI library.
+@@ -1059,6 +1053,11 @@ command line will be passed untouched to the GSSAPI library.
  The default is
  .Dq no .
  .It Cm GSSAPIKexAlgorithms
@@ -91,7 +91,7 @@ index 9e5163774..306fa1d8a 100644
  The list of key exchange algorithms that are offered for GSSAPI
  key exchange. Possible values are
  .Bd -literal -offset 3n
-@@ -1034,9 +1033,6 @@ gss-nistp256-sha256-,
+@@ -1071,9 +1070,6 @@ gss-nistp256-sha256-,
  gss-curve25519-sha256-
  .Ed
  .Pp
@@ -101,7 +101,7 @@ index 9e5163774..306fa1d8a 100644
  This option only applies to connections using GSSAPI.
  .Pp
  The list of supported key exchange algorithms may also be obtained using
-@@ -1056,38 +1053,25 @@ will not be converted automatically,
+@@ -1096,38 +1092,25 @@ will not be converted automatically,
  but may be manually hashed using
  .Xr ssh-keygen 1 .
  .It Cm HostbasedAcceptedAlgorithms
@@ -148,7 +148,7 @@ index 9e5163774..306fa1d8a 100644
  .Pp
  The
  .Fl Q
-@@ -1142,6 +1126,17 @@ to prefer their algorithms.
+@@ -1182,6 +1165,17 @@ to prefer their algorithms.
  .Pp
  The list of available signature algorithms may also be obtained using
  .Qq ssh -Q HostKeyAlgorithms .
@@ -166,7 +166,7 @@ index 9e5163774..306fa1d8a 100644
  .It Cm HostKeyAlias
  Specifies an alias that should be used instead of the
  real host name when looking up or saving the host key
-@@ -1364,6 +1359,11 @@ it may be zero or more of:
+@@ -1379,6 +1373,11 @@ it may be zero or more of:
  and
  .Cm pam .
  .It Cm KexAlgorithms
@@ -178,7 +178,7 @@ index 9e5163774..306fa1d8a 100644
  Specifies the permitted KEX (Key Exchange) algorithms that will be used and
  their preference order.
  The selected algorithm will be the first algorithm in this list that
-@@ -1375,28 +1374,16 @@ Multiple algorithms must be comma-separated.
+@@ -1387,28 +1386,16 @@ Multiple algorithms must be comma-separated.
  .Pp
  If the specified list begins with a
  .Sq +
@@ -211,7 +211,7 @@ index 9e5163774..306fa1d8a 100644
  .Pp
  The list of supported key exchange algorithms may also be obtained using
  .Qq ssh -Q kex .
-@@ -1510,37 +1498,33 @@ function, and all code in the
+@@ -1525,37 +1512,33 @@ function, and all code in the
  file.
  This option is intended for debugging and no overrides are enabled by default.
  .It Cm MACs
@@ -258,7 +258,7 @@ index 9e5163774..306fa1d8a 100644
  The list of available MAC algorithms may also be obtained using
  .Qq ssh -Q mac .
  .It Cm NoHostAuthenticationForLocalhost
-@@ -1729,41 +1713,31 @@ instead of continuing to execute and pass data.
+@@ -1744,41 +1727,31 @@ instead of continuing to execute and pass data.
  The default is
  .Cm no .
  .It Cm PubkeyAcceptedAlgorithms
@@ -312,7 +312,7 @@ index 9e5163774..306fa1d8a 100644
  .It Cm PubkeyAuthentication
  Specifies whether to try public key authentication.
  The argument to this keyword must be
-@@ -2517,7 +2491,9 @@ for those users who do not have a configuration file.
+@@ -2532,7 +2505,9 @@ for those users who do not have a configuration file.
  This file must be world-readable.
  .El
  .Sh SEE ALSO
@@ -324,7 +324,7 @@ index 9e5163774..306fa1d8a 100644
  .An -nosplit
  OpenSSH is a derivative of the original and free
 diff --git a/sshd_config.5 b/sshd_config.5
-index 1762874c4..1d16dabaf 100644
+index c3a4806e0..5998275d2 100644
 --- a/sshd_config.5
 +++ b/sshd_config.5
 @@ -383,17 +383,13 @@ If the argument is
@@ -394,7 +394,7 @@ index 1762874c4..1d16dabaf 100644
  The list of available ciphers may also be obtained using
  .Qq ssh -Q cipher .
  .It Cm ClientAliveCountMax
-@@ -789,58 +783,46 @@ For this to work
+@@ -797,58 +791,46 @@ For this to work
  .Cm GSSAPIKeyExchange
  needs to be enabled in the server and also used by the client.
  .It Cm GSSAPIKexAlgorithms
@@ -472,7 +472,7 @@ index 1762874c4..1d16dabaf 100644
  .Pp
  The list of available signature algorithms may also be obtained using
  .Qq ssh -Q HostbasedAcceptedAlgorithms .
-@@ -904,27 +886,13 @@ is specified, the location of the socket will be read from the
+@@ -915,27 +897,13 @@ is specified, the location of the socket will be read from the
  .Ev SSH_AUTH_SOCK
  environment variable.
  .It Cm HostKeyAlgorithms
@@ -505,7 +505,7 @@ index 1762874c4..1d16dabaf 100644
  The list of available signature algorithms may also be obtained using
  .Qq ssh -Q HostKeyAlgorithms .
  .It Cm IgnoreRhosts
-@@ -1070,6 +1038,11 @@ Specifies whether to look at .k5login file for user's aliases.
+@@ -1081,6 +1049,11 @@ Specifies whether to look at .k5login file for user's aliases.
  The default is
  .Cm yes .
  .It Cm KexAlgorithms
@@ -517,7 +517,7 @@ index 1762874c4..1d16dabaf 100644
  Specifies the permitted KEX (Key Exchange) algorithms that the server will
  offer to clients.
  The ordering of this list is not important, as the client specifies the
-@@ -1078,16 +1051,16 @@ Multiple algorithms must be comma-separated.
+@@ -1089,16 +1062,16 @@ Multiple algorithms must be comma-separated.
  .Pp
  If the specified list begins with a
  .Sq +
@@ -538,7 +538,7 @@ index 1762874c4..1d16dabaf 100644
  .Pp
  The supported algorithms are:
  .Pp
-@@ -1124,14 +1097,6 @@ sntrup761x25519-sha512
+@@ -1135,14 +1108,6 @@ sntrup761x25519-sha512
  sntrup761x25519-sha512@openssh.com
  .El
  .Pp
@@ -553,7 +553,7 @@ index 1762874c4..1d16dabaf 100644
  The list of supported key exchange algorithms may also be obtained using
  .Qq ssh -Q KexAlgorithms .
  .It Cm ListenAddress
-@@ -1218,21 +1183,26 @@ function, and all code in the
+@@ -1229,21 +1194,26 @@ function, and all code in the
  file.
  This option is intended for debugging and no overrides are enabled by default.
  .It Cm MACs
@@ -584,7 +584,7 @@ index 1762874c4..1d16dabaf 100644
  .Pp
  The algorithms that contain
  .Qq -etm
-@@ -1275,15 +1245,6 @@ umac-64-etm@openssh.com
+@@ -1286,15 +1256,6 @@ umac-64-etm@openssh.com
  umac-128-etm@openssh.com
  .El
  .Pp
@@ -600,7 +600,7 @@ index 1762874c4..1d16dabaf 100644
  The list of available MAC algorithms may also be obtained using
  .Qq ssh -Q mac .
  .It Cm Match
-@@ -1773,38 +1734,25 @@ or equivalent.)
+@@ -1784,38 +1745,25 @@ or equivalent.)
  The default is
  .Cm yes .
  .It Cm PubkeyAcceptedAlgorithms
@@ -648,7 +648,7 @@ index 1762874c4..1d16dabaf 100644
  .Pp
  The list of available signature algorithms may also be obtained using
  .Qq ssh -Q PubkeyAcceptedAlgorithms .
-@@ -2317,7 +2265,9 @@ This file should be writable by root only, but it is recommended
+@@ -2328,7 +2276,9 @@ This file should be writable by root only, but it is recommended
  .El
  .Sh SEE ALSO
  .Xr sftp-server 8 ,
@@ -660,5 +660,5 @@ index 1762874c4..1d16dabaf 100644
  .An -nosplit
  OpenSSH is a derivative of the original and free
 -- 
-2.53.0
+2.55.0
 

diff --git a/0028-openssh-8.0p1-openssl-kdf.patch b/0028-openssh-8.0p1-openssl-kdf.patch
index 053daad..faeb93a 100644
--- a/0028-openssh-8.0p1-openssl-kdf.patch
+++ b/0028-openssh-8.0p1-openssl-kdf.patch
@@ -1,4 +1,4 @@
-From 1a2e4068f997c1a9ed4179671162201f766f20b8 Mon Sep 17 00:00:00 2001
+From 2927324b981fd932b46fcb53e0399f701c528dc9 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 28/54] openssh-8.0p1-openssl-kdf
@@ -10,7 +10,7 @@ Subject: [PATCH 28/54] openssh-8.0p1-openssl-kdf
  2 files changed, 108 insertions(+)
 
 diff --git a/configure.ac b/configure.ac
-index 75576b45d..86f0e9e09 100644
+index 321adbab1..caa75f708 100644
 --- a/configure.ac
 +++ b/configure.ac
 @@ -3248,6 +3248,7 @@ if test "x$openssl" = "xyes" ; then
@@ -22,7 +22,7 @@ index 75576b45d..86f0e9e09 100644
  
  	# LibreSSL/OpenSSL API differences
 diff --git a/kex.c b/kex.c
-index 3608a5504..54e711810 100644
+index 3701a40c0..5b6066169 100644
 --- a/kex.c
 +++ b/kex.c
 @@ -37,6 +37,11 @@
@@ -37,7 +37,7 @@ index 3608a5504..54e711810 100644
  #endif
  
  #include "ssh.h"
-@@ -1083,6 +1088,107 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
+@@ -1088,6 +1093,107 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
  	return r;
  }
  
@@ -145,7 +145,7 @@ index 3608a5504..54e711810 100644
  static int
  derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen,
      const struct sshbuf *shared_secret, u_char **keyp)
-@@ -1146,6 +1252,7 @@ derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen,
+@@ -1151,6 +1257,7 @@ derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen,
  	ssh_digest_free(hashctx);
  	return r;
  }
@@ -154,5 +154,5 @@ index 3608a5504..54e711810 100644
  #define NKEYS	6
  int
 -- 
-2.53.0
+2.55.0
 

diff --git a/0029-openssh-8.2p1-visibility.patch b/0029-openssh-8.2p1-visibility.patch
index 3461efa..b762575 100644
--- a/0029-openssh-8.2p1-visibility.patch
+++ b/0029-openssh-8.2p1-visibility.patch
@@ -1,4 +1,4 @@
-From 03f96498c3b1c5a01014a47a800f94430c7c92ce Mon Sep 17 00:00:00 2001
+From 8ec6e23e9f608226d2a28c927deb8707c2c05d19 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 29/54] openssh-8.2p1-visibility
@@ -49,5 +49,5 @@ index 4c96e8827..4af5209db 100644
      struct sk_resident_key ***rks, size_t *nrks)
  {
 -- 
-2.53.0
+2.55.0
 

diff --git a/0030-openssh-8.2p1-x11-without-ipv6.patch b/0030-openssh-8.2p1-x11-without-ipv6.patch
index 8760132..24987be 100644
--- a/0030-openssh-8.2p1-x11-without-ipv6.patch
+++ b/0030-openssh-8.2p1-x11-without-ipv6.patch
@@ -1,4 +1,4 @@
-From efca1f54d8afc867f3989b5ed790054de942cb0c Mon Sep 17 00:00:00 2001
+From 4fe1b0e966089b0f073f11134b4745ccce463547 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:28 +0200
 Subject: [PATCH 30/54] openssh-8.2p1-x11-without-ipv6
@@ -9,10 +9,10 @@ Subject: [PATCH 30/54] openssh-8.2p1-x11-without-ipv6
  1 file changed, 10 insertions(+)
 
 diff --git a/channels.c b/channels.c
-index 71cc67d17..14d536d64 100644
+index fcd80288f..bae84e5f9 100644
 --- a/channels.c
 +++ b/channels.c
-@@ -5132,6 +5132,16 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
+@@ -5141,6 +5141,16 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
  				debug2_f("bind port %d: %.100s", port,
  				    strerror(errno));
  				close(sock);
@@ -30,5 +30,5 @@ index 71cc67d17..14d536d64 100644
  					close(socks[n]);
  				num_socks = 0;
 -- 
-2.53.0
+2.55.0
 

diff --git a/0031-openssh-8.0p1-preserve-pam-errors.patch b/0031-openssh-8.0p1-preserve-pam-errors.patch
index 2894ebc..846cd7b 100644
--- a/0031-openssh-8.0p1-preserve-pam-errors.patch
+++ b/0031-openssh-8.0p1-preserve-pam-errors.patch
@@ -1,4 +1,4 @@
-From 8203803a93ad6b807a94324156ef866b45cc8eae Mon Sep 17 00:00:00 2001
+From d02d2011c9fb5f078f854e59ff4cfae851ddc8a9 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 31/54] openssh-8.0p1-preserve-pam-errors
@@ -53,5 +53,5 @@ index 3f342f9e1..d3dcd037b 100644
  				sshpam_set_maxtries_reached(1);
  			/* FALLTHROUGH */
 -- 
-2.53.0
+2.55.0
 

diff --git a/0032-openssh-8.7p1-scp-kill-switch.patch b/0032-openssh-8.7p1-scp-kill-switch.patch
index 947a6b9..ceff7db 100644
--- a/0032-openssh-8.7p1-scp-kill-switch.patch
+++ b/0032-openssh-8.7p1-scp-kill-switch.patch
@@ -1,4 +1,4 @@
-From 61acc5e94ea087db20504777e74bdb5591762056 Mon Sep 17 00:00:00 2001
+From 40e36d72995b4f013d65e4688bb6aa611e08b178 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 32/54] openssh-8.7p1-scp-kill-switch
@@ -11,12 +11,12 @@ Subject: [PATCH 32/54] openssh-8.7p1-scp-kill-switch
  3 files changed, 16 insertions(+)
 
 diff --git a/pathnames.h b/pathnames.h
-index ae01c69e2..80db76e00 100644
+index 2f0b9f4f6..ce5ddc14f 100644
 --- a/pathnames.h
 +++ b/pathnames.h
-@@ -40,6 +40,7 @@
- #define _PATH_HOST_RSA_KEY_FILE		SSHDIR "/ssh_host_rsa_key"
+@@ -41,6 +41,7 @@
  #define _PATH_HOST_ED25519_KEY_FILE	SSHDIR "/ssh_host_ed25519_key"
+ #define _PATH_HOST_MLDSA44_ED25519_KEY_FILE SSHDIR "/ssh_host_mldsa44_ed25519_key"
  #define _PATH_DH_MODULI			SSHDIR "/moduli"
 +#define _PATH_SCP_KILL_SWITCH		SSHDIR "/disable_scp"
  
@@ -41,7 +41,7 @@ index 7bce0fe6f..2bb838c7d 100644
  .Ex -std scp
  .Sh SEE ALSO
 diff --git a/scp.c b/scp.c
-index 5618e28e7..76dcb6040 100644
+index ac43ec5f4..5775be457 100644
 --- a/scp.c
 +++ b/scp.c
 @@ -628,6 +628,14 @@ main(int argc, char **argv)
@@ -60,5 +60,5 @@ index 5618e28e7..76dcb6040 100644
  		fatal("unknown user %u", (u_int) userid);
  
 -- 
-2.53.0
+2.55.0
 

diff --git a/0033-openssh-8.7p1-recursive-scp.patch b/0033-openssh-8.7p1-recursive-scp.patch
index 33f1067..f6462c6 100644
--- a/0033-openssh-8.7p1-recursive-scp.patch
+++ b/0033-openssh-8.7p1-recursive-scp.patch
@@ -1,4 +1,4 @@
-From 72223f519294c234e7313dc0efb86c1164a67b47 Mon Sep 17 00:00:00 2001
+From 1e6712a333655da6dd01f2b9d8b0b2688f99082a Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 33/54] openssh-8.7p1-recursive-scp
@@ -15,7 +15,7 @@ Subject: [PATCH 33/54] openssh-8.7p1-recursive-scp
  4 files changed, 52 insertions(+), 20 deletions(-)
 
 diff --git a/scp.c b/scp.c
-index 76dcb6040..973848e04 100644
+index 5775be457..646cf2478 100644
 --- a/scp.c
 +++ b/scp.c
 @@ -1377,7 +1377,7 @@ source_sftp(int argc, char *src, char *targ, struct sftp_conn *conn)
@@ -28,7 +28,7 @@ index 76dcb6040..973848e04 100644
  			errs = 1;
  		}
 diff --git a/sftp-client.c b/sftp-client.c
-index 69ef28cdc..3012eac12 100644
+index 1f031128e..aad864357 100644
 --- a/sftp-client.c
 +++ b/sftp-client.c
 @@ -988,7 +988,7 @@ sftp_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
@@ -113,7 +113,7 @@ index 69ef28cdc..3012eac12 100644
  }
  
  int
-@@ -2001,7 +2033,7 @@ sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst,
+@@ -2006,7 +2038,7 @@ sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst,
  	char *src_canon;
  	int ret;
  
@@ -122,7 +122,7 @@ index 69ef28cdc..3012eac12 100644
  		error("download \"%s\": path canonicalization failed", src);
  		return -1;
  	}
-@@ -2356,12 +2388,12 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
+@@ -2361,12 +2393,12 @@ upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
  int
  sftp_upload_dir(struct sftp_conn *conn, const char *src, const char *dst,
      int preserve_flag, int print_flag, int resume, int fsync_flag,
@@ -137,7 +137,7 @@ index 69ef28cdc..3012eac12 100644
  		error("upload \"%s\": path canonicalization failed", dst);
  		return -1;
  	}
-@@ -2819,7 +2851,7 @@ sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
+@@ -2829,7 +2861,7 @@ sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
  	char *from_path_canon;
  	int ret;
  
@@ -169,10 +169,10 @@ index cc8e20298..36b68d4cc 100644
  /*
   * Download a 'from_path' from the 'from' connection and upload it to
 diff --git a/sftp.c b/sftp.c
-index eebb166e8..4a71dd05d 100644
+index 31d8fa494..d3be72ffe 100644
 --- a/sftp.c
 +++ b/sftp.c
-@@ -805,7 +805,7 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
+@@ -804,7 +804,7 @@ process_put(struct sftp_conn *conn, const char *src, const char *dst,
  		    (rflag || global_rflag)) {
  			if (sftp_upload_dir(conn, g.gl_pathv[i], abs_dst,
  			    pflag || global_pflag, 1, resume,
@@ -181,7 +181,7 @@ index eebb166e8..4a71dd05d 100644
  				err = -1;
  		} else {
  			if (sftp_upload(conn, g.gl_pathv[i], abs_dst,
-@@ -1640,7 +1640,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
+@@ -1649,7 +1649,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
  		if (path1 == NULL || *path1 == '\0')
  			path1 = xstrdup(startdir);
  		path1 = sftp_make_absolute(path1, *pwd);
@@ -190,7 +190,7 @@ index eebb166e8..4a71dd05d 100644
  			err = 1;
  			break;
  		}
-@@ -2266,7 +2266,7 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
+@@ -2275,7 +2275,7 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
  	}
  #endif /* USE_LIBEDIT */
  
@@ -200,5 +200,5 @@ index eebb166e8..4a71dd05d 100644
  	startdir = xstrdup(remote_path);
  
 -- 
-2.53.0
+2.55.0
 

diff --git a/0034-openssh-8.7p1-ibmca.patch b/0034-openssh-8.7p1-ibmca.patch
index 553297a..58ae865 100644
--- a/0034-openssh-8.7p1-ibmca.patch
+++ b/0034-openssh-8.7p1-ibmca.patch
@@ -1,4 +1,4 @@
-From 27e73c9d6c4a44bdfb98852ddeb30e902ff8658e Mon Sep 17 00:00:00 2001
+From 16d7ae47eddc85a0360b53c2e3063395348ccf3d Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 34/54] openssh-8.7p1-ibmca
@@ -23,5 +23,5 @@ index f61124585..417c20484 100644
  #include <sys/types.h>
  #include <unistd.h>
 -- 
-2.53.0
+2.55.0
 

diff --git a/0035-openssh-7.6p1-audit.patch b/0035-openssh-7.6p1-audit.patch
index 40df06c..44dd986 100644
--- a/0035-openssh-7.6p1-audit.patch
+++ b/0035-openssh-7.6p1-audit.patch
@@ -1,4 +1,4 @@
-From 00ef565336c933f9464b09c55134fa3b3c76ad40 Mon Sep 17 00:00:00 2001
+From 134511907ac99564a417a5b45e3ea13d4afbf8ae Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 35/54] openssh-7.6p1-audit
@@ -35,10 +35,10 @@ Subject: [PATCH 35/54] openssh-7.6p1-audit
  create mode 100644 auditstub.c
 
 diff --git a/Makefile.in b/Makefile.in
-index a12f6ad1d..f80c95846 100644
+index 106306024..56c4c88cf 100644
 --- a/Makefile.in
 +++ b/Makefile.in
-@@ -110,7 +110,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+@@ -111,7 +111,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
  	kexsntrup761x25519.o kexmlkem768x25519.o sntrup761.o kexgen.o \
  	kexgssc.o \
  	sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
@@ -796,10 +796,10 @@ index 142ac5afd..1858bddab 100644
  	}
  	if (!allowed_user(ssh, pw))
 diff --git a/auth.h b/auth.h
-index 815f821f9..b72b76163 100644
+index 9dd25f494..066f5effa 100644
 --- a/auth.h
 +++ b/auth.h
-@@ -213,6 +213,8 @@ struct sshkey	*get_hostkey_private_by_type(int, int, struct ssh *);
+@@ -214,6 +214,8 @@ struct sshkey	*get_hostkey_private_by_type(int, int, struct ssh *);
  int	 get_hostkey_index(struct sshkey *, int, struct ssh *);
  int	 sshd_hostkey_sign(struct ssh *, struct sshkey *, struct sshkey *,
      u_char **, size_t *, const u_char *, size_t, const char *);
@@ -808,7 +808,7 @@ index 815f821f9..b72b76163 100644
  
  /* Key / cert options linkage to auth layer */
  int	 auth_activate_options(struct ssh *, struct sshauthopt *);
-@@ -238,6 +240,8 @@ int	 auth_check_authkey_line(struct passwd *, struct sshkey *,
+@@ -239,6 +241,8 @@ int	 auth_check_authkey_line(struct passwd *, struct sshkey *,
      char *, const char *, const char *, const char *, struct sshauthopt **);
  int	 auth_check_authkeys_file(struct passwd *, FILE *, char *,
      struct sshkey *, const char *, const char *, struct sshauthopt **);
@@ -886,10 +886,10 @@ index 319e42b2b..973810dc9 100644
  match_principals_file(struct passwd *pw, char *file,
      struct sshkey_cert *cert, struct sshauthopt **authoptsp)
 diff --git a/auth2.c b/auth2.c
-index 3c3bdb776..790bb3454 100644
+index f0f39707f..71460d78f 100644
 --- a/auth2.c
 +++ b/auth2.c
-@@ -315,9 +315,6 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
+@@ -321,9 +321,6 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
  			authctxt->valid = 0;
  			/* Invalid user, fake password information */
  			authctxt->pw = fakepw();
@@ -900,7 +900,7 @@ index 3c3bdb776..790bb3454 100644
  #ifdef USE_PAM
  		if (options.use_pam)
 diff --git a/cipher.c b/cipher.c
-index f770e666c..b05755d73 100644
+index 0697e559b..82076d4bd 100644
 --- a/cipher.c
 +++ b/cipher.c
 @@ -63,25 +63,6 @@ struct sshcipher_ctx {
@@ -929,7 +929,7 @@ index f770e666c..b05755d73 100644
  static const struct sshcipher ciphers[] = {
  #ifdef WITH_OPENSSL
  #ifndef OPENSSL_NO_DES
-@@ -410,7 +391,7 @@ cipher_get_length(struct sshcipher_ctx *cc, u_int *plenp, u_int seqnr,
+@@ -418,7 +399,7 @@ cipher_get_length(struct sshcipher_ctx *cc, u_int *plenp, u_int seqnr,
  void
  cipher_free(struct sshcipher_ctx *cc)
  {
@@ -939,7 +939,7 @@ index f770e666c..b05755d73 100644
  	if ((cc->cipher->flags & CFLAG_CHACHAPOLY) != 0) {
  		chachapoly_free(cc->cp_ctx);
 diff --git a/cipher.h b/cipher.h
-index 6533ff2bb..2e05a0210 100644
+index 32abdde22..e953dfc3c 100644
 --- a/cipher.h
 +++ b/cipher.h
 @@ -47,7 +47,25 @@
@@ -970,7 +970,7 @@ index 6533ff2bb..2e05a0210 100644
  
  const struct sshcipher *cipher_by_name(const char *);
 diff --git a/kex.c b/kex.c
-index 54e711810..70bebce6a 100644
+index 5b6066169..3fbf1dd43 100644
 --- a/kex.c
 +++ b/kex.c
 @@ -64,6 +64,7 @@
@@ -981,7 +981,7 @@ index 54e711810..70bebce6a 100644
  
  /* prototype */
  static int kex_choose_conf(struct ssh *, uint32_t seq);
-@@ -826,12 +827,16 @@ kex_start_rekex(struct ssh *ssh)
+@@ -831,12 +832,16 @@ kex_start_rekex(struct ssh *ssh)
  }
  
  static int
@@ -1000,7 +1000,7 @@ index 54e711810..70bebce6a 100644
  	if ((enc->cipher = cipher_by_name(name)) == NULL) {
  		error_f("unsupported cipher %s", name);
  		free(name);
-@@ -852,8 +857,12 @@ choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server)
+@@ -857,8 +862,12 @@ choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server)
  {
  	char *name = match_list(client, server, NULL);
  
@@ -1014,7 +1014,7 @@ index 54e711810..70bebce6a 100644
  	if (mac_setup(mac, name) < 0) {
  		error_f("unsupported MAC %s", name);
  		free(name);
-@@ -866,12 +875,16 @@ choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server)
+@@ -871,12 +880,16 @@ choose_mac(struct ssh *ssh, struct sshmac *mac, char *client, char *server)
  }
  
  static int
@@ -1033,7 +1033,7 @@ index 54e711810..70bebce6a 100644
  #ifdef WITH_ZLIB
  	if (strcmp(name, "zlib@openssh.com") == 0) {
  		comp->type = COMP_DELAYED;
-@@ -1035,7 +1048,7 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
+@@ -1040,7 +1053,7 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
  		nenc  = ctos ? PROPOSAL_ENC_ALGS_CTOS  : PROPOSAL_ENC_ALGS_STOC;
  		nmac  = ctos ? PROPOSAL_MAC_ALGS_CTOS  : PROPOSAL_MAC_ALGS_STOC;
  		ncomp = ctos ? PROPOSAL_COMP_ALGS_CTOS : PROPOSAL_COMP_ALGS_STOC;
@@ -1042,7 +1042,7 @@ index 54e711810..70bebce6a 100644
  		    sprop[nenc])) != 0) {
  			kex->failed_choice = peer[nenc];
  			peer[nenc] = NULL;
-@@ -1050,7 +1063,7 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
+@@ -1055,7 +1068,7 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
  			peer[nmac] = NULL;
  			goto out;
  		}
@@ -1051,7 +1051,7 @@ index 54e711810..70bebce6a 100644
  		    sprop[ncomp])) != 0) {
  			kex->failed_choice = peer[ncomp];
  			peer[ncomp] = NULL;
-@@ -1073,6 +1086,10 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
+@@ -1078,6 +1091,10 @@ kex_choose_conf(struct ssh *ssh, uint32_t seq)
  		dh_need = MAXIMUM(dh_need, newkeys->enc.block_size);
  		dh_need = MAXIMUM(dh_need, newkeys->enc.iv_len);
  		dh_need = MAXIMUM(dh_need, newkeys->mac.key_len);
@@ -1062,7 +1062,7 @@ index 54e711810..70bebce6a 100644
  	}
  	/* XXX need runden? */
  	kex->we_need = need;
-@@ -1342,6 +1359,36 @@ dump_digest(const char *msg, const u_char *digest, int len)
+@@ -1347,6 +1364,36 @@ dump_digest(const char *msg, const u_char *digest, int len)
  }
  #endif
  
@@ -1100,10 +1100,10 @@ index 54e711810..70bebce6a 100644
   * Send a plaintext error message to the peer, suffixed by \r\n.
   * Only used during banner exchange, and there only for the server.
 diff --git a/kex.h b/kex.h
-index 2c86b25f9..0a0cb1562 100644
+index d229df7e6..692e28363 100644
 --- a/kex.h
 +++ b/kex.h
-@@ -261,6 +261,8 @@ int	 kexgss_client(struct ssh *);
+@@ -262,6 +262,8 @@ int	 kexgss_client(struct ssh *);
  int	 kexgss_server(struct ssh *);
  #endif
  
@@ -1113,7 +1113,7 @@ index 2c86b25f9..0a0cb1562 100644
  int	 kex_dh_enc(struct kex *, const struct sshbuf *, struct sshbuf **,
      struct sshbuf **);
 diff --git a/mac.c b/mac.c
-index 17607830c..2913b8a8c 100644
+index 30496b402..d47df0cdf 100644
 --- a/mac.c
 +++ b/mac.c
 @@ -230,6 +230,20 @@ mac_clear(struct sshmac *mac)
@@ -1149,7 +1149,7 @@ index 04089f41b..6b5ebc44b 100644
  
  #endif /* SSHMAC_H */
 diff --git a/monitor.c b/monitor.c
-index 5255083ab..305883278 100644
+index 7e1c9061c..7dfc7976b 100644
 --- a/monitor.c
 +++ b/monitor.c
 @@ -83,6 +83,7 @@
@@ -1204,7 +1204,7 @@ index 5255083ab..305883278 100644
  #endif
      {0, 0, NULL}
  };
-@@ -1603,8 +1620,10 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1577,8 +1594,10 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
  	int r, ret, req_presence = 0, req_verify = 0, valid_data = 0;
  	int encoded_ret;
  	struct sshkey_sig_details *sig_details = NULL;
@@ -1216,7 +1216,7 @@ index 5255083ab..305883278 100644
  	    (r = sshbuf_get_string_direct(m, &signature, &signaturelen)) != 0 ||
  	    (r = sshbuf_get_string_direct(m, &data, &datalen)) != 0 ||
  	    (r = sshbuf_get_cstring(m, &sigalg, NULL)) != 0)
-@@ -1613,6 +1632,8 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1587,6 +1606,8 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
  	if (hostbased_cuser == NULL || hostbased_chost == NULL ||
  	  !monitor_allowed_key(blob, bloblen))
  		fatal_f("bad key, not previously allowed");
@@ -1225,7 +1225,7 @@ index 5255083ab..305883278 100644
  
  	/* Empty signature algorithm means NULL. */
  	if (*sigalg == '\0') {
-@@ -1628,14 +1649,19 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1602,14 +1623,19 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
  	case MM_USERKEY:
  		valid_data = monitor_valid_userblob(ssh, data, datalen);
  		auth_method = "publickey";
@@ -1245,7 +1245,7 @@ index 5255083ab..305883278 100644
  		break;
  	}
  	if (!valid_data)
-@@ -1647,8 +1673,6 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1621,8 +1647,6 @@ mm_answer_keyverify(struct ssh *ssh, int sock, struct sshbuf *m)
  	    SSH_FP_DEFAULT)) == NULL)
  		fatal_f("sshkey_fingerprint failed");
  
@@ -1254,7 +1254,7 @@ index 5255083ab..305883278 100644
  	debug3_f("%s %s signature using %s %s%s%s", auth_method,
  	    sshkey_type(key), sigalg == NULL ? "default" : sigalg,
  	    (ret == 0) ? "verified" : "unverified",
-@@ -1736,13 +1760,19 @@ mm_record_login(struct ssh *ssh, Session *s, struct passwd *pw)
+@@ -1710,13 +1734,19 @@ mm_record_login(struct ssh *ssh, Session *s, struct passwd *pw)
  }
  
  static void
@@ -1275,7 +1275,7 @@ index 5255083ab..305883278 100644
  	session_unused(s->self);
  }
  
-@@ -1809,7 +1839,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1783,7 +1813,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
  
   error:
  	if (s != NULL)
@@ -1284,7 +1284,7 @@ index 5255083ab..305883278 100644
  	if ((r = sshbuf_put_u32(m, 0)) != 0)
  		fatal_fr(r, "assemble 0");
  	mm_request_send(sock, MONITOR_ANS_PTY, m);
-@@ -1828,7 +1858,7 @@ mm_answer_pty_cleanup(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1802,7 +1832,7 @@ mm_answer_pty_cleanup(struct ssh *ssh, int sock, struct sshbuf *m)
  	if ((r = sshbuf_get_cstring(m, &tty, NULL)) != 0)
  		fatal_fr(r, "parse tty");
  	if ((s = session_by_tty(tty)) != NULL)
@@ -1293,7 +1293,7 @@ index 5255083ab..305883278 100644
  	sshbuf_reset(m);
  	free(tty);
  	return (0);
-@@ -1850,6 +1880,8 @@ mm_answer_term(struct ssh *ssh, int sock, struct sshbuf *req)
+@@ -1824,6 +1854,8 @@ mm_answer_term(struct ssh *ssh, int sock, struct sshbuf *req)
  		sshpam_cleanup();
  #endif
  
@@ -1302,7 +1302,7 @@ index 5255083ab..305883278 100644
  	while (waitpid(pmonitor->m_pid, &status, 0) == -1)
  		if (errno != EINTR)
  			exit(1);
-@@ -1896,12 +1928,46 @@ mm_answer_audit_command(struct ssh *ssh, int socket, struct sshbuf *m)
+@@ -1870,12 +1902,46 @@ mm_answer_audit_command(struct ssh *ssh, int socket, struct sshbuf *m)
  {
  	char *cmd;
  	int r;
@@ -1350,7 +1350,7 @@ index 5255083ab..305883278 100644
  	free(cmd);
  	return (0);
  }
-@@ -1974,6 +2040,7 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
+@@ -1948,6 +2014,7 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
  void
  mm_get_keystate(struct ssh *ssh, struct monitor *pmonitor)
  {
@@ -1358,7 +1358,7 @@ index 5255083ab..305883278 100644
  	debug3_f("Waiting for new keys");
  
  	if ((child_state = sshbuf_new()) == NULL)
-@@ -1981,6 +2048,19 @@ mm_get_keystate(struct ssh *ssh, struct monitor *pmonitor)
+@@ -1955,6 +2022,19 @@ mm_get_keystate(struct ssh *ssh, struct monitor *pmonitor)
  	mm_request_receive_expect(pmonitor->m_sendfd, MONITOR_REQ_KEYEXPORT,
  	    child_state);
  	debug3_f("GOT new keys");
@@ -1378,7 +1378,7 @@ index 5255083ab..305883278 100644
  }
  
  
-@@ -2275,3 +2355,102 @@ mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) {
+@@ -2249,3 +2329,102 @@ mm_answer_gss_updatecreds(struct ssh *ssh, int socket, struct sshbuf *m) {
  
  #endif /* GSSAPI */
  
@@ -1501,10 +1501,10 @@ index f4721c472..087addff5 100644
  	MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151,
  	MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153,
 diff --git a/monitor_wrap.c b/monitor_wrap.c
-index 496de436e..ef419b743 100644
+index 2d905c16a..7ccf8729c 100644
 --- a/monitor_wrap.c
 +++ b/monitor_wrap.c
-@@ -598,7 +598,7 @@ mm_key_allowed(enum mm_keytype type, const char *user, const char *host,
+@@ -563,7 +563,7 @@ mm_key_allowed(enum mm_keytype type, const char *user, const char *host,
   */
  
  int
@@ -1513,7 +1513,7 @@ index 496de436e..ef419b743 100644
      const u_char *data, size_t datalen, const char *sigalg, u_int compat,
      struct sshkey_sig_details **sig_detailsp)
  {
-@@ -614,7 +614,8 @@ mm_sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
+@@ -579,7 +579,8 @@ mm_sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
  		*sig_detailsp = NULL;
  	if ((m = sshbuf_new()) == NULL)
  		fatal_f("sshbuf_new failed");
@@ -1523,7 +1523,7 @@ index 496de436e..ef419b743 100644
  	    (r = sshbuf_put_string(m, sig, siglen)) != 0 ||
  	    (r = sshbuf_put_string(m, data, datalen)) != 0 ||
  	    (r = sshbuf_put_cstring(m, sigalg == NULL ? "" : sigalg)) != 0)
-@@ -647,6 +648,22 @@ mm_sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
+@@ -612,6 +613,22 @@ mm_sshkey_verify(const struct sshkey *key, const u_char *sig, size_t siglen,
  	return 0;
  }
  
@@ -1546,7 +1546,7 @@ index 496de436e..ef419b743 100644
  void
  mm_send_keystate(struct ssh *ssh, struct monitor *monitor)
  {
-@@ -1063,11 +1080,12 @@ mm_audit_event(struct ssh *ssh, ssh_audit_event_t event)
+@@ -1011,11 +1028,12 @@ mm_audit_event(struct ssh *ssh, ssh_audit_event_t event)
  	sshbuf_free(m);
  }
  
@@ -1561,7 +1561,7 @@ index 496de436e..ef419b743 100644
  
  	debug3_f("entering command %s", command);
  
-@@ -1077,6 +1095,30 @@ mm_audit_run_command(const char *command)
+@@ -1025,6 +1043,30 @@ mm_audit_run_command(const char *command)
  		fatal_fr(r, "buffer error");
  
  	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_COMMAND, m);
@@ -1592,7 +1592,7 @@ index 496de436e..ef419b743 100644
  	sshbuf_free(m);
  }
  #endif /* SSH_AUDIT_EVENTS */
-@@ -1325,3 +1367,83 @@ server_get_connection_info(struct ssh *ssh, int populate, int use_dns)
+@@ -1273,3 +1315,83 @@ server_get_connection_info(struct ssh *ssh, int populate, int use_dns)
  	return &ci;
  }
  
@@ -1677,7 +1677,7 @@ index 496de436e..ef419b743 100644
 +}
 +#endif /* SSH_AUDIT_EVENTS */
 diff --git a/monitor_wrap.h b/monitor_wrap.h
-index a708bf4d8..9856968f9 100644
+index 07b207d1d..7f846f085 100644
 --- a/monitor_wrap.h
 +++ b/monitor_wrap.h
 @@ -63,7 +63,9 @@ int mm_user_key_allowed(struct ssh *ssh, struct passwd *, struct sshkey *, int,
@@ -1706,7 +1706,7 @@ index a708bf4d8..9856968f9 100644
  
  struct Session;
 diff --git a/packet.c b/packet.c
-index 190a579d1..fa6f4c843 100644
+index 4c89e60c5..5cf682b0e 100644
 --- a/packet.c
 +++ b/packet.c
 @@ -77,6 +77,7 @@
@@ -1717,7 +1717,7 @@ index 190a579d1..fa6f4c843 100644
  #include "compat.h"
  #include "ssh2.h"
  #include "cipher.h"
-@@ -511,6 +512,13 @@ ssh_packet_get_connection_out(struct ssh *ssh)
+@@ -516,6 +517,13 @@ ssh_packet_get_connection_out(struct ssh *ssh)
  	return ssh->state->connection_out;
  }
  
@@ -1731,7 +1731,7 @@ index 190a579d1..fa6f4c843 100644
  /*
   * Returns the IP-address of the remote host as a string.  The returned
   * string must not be freed.
-@@ -681,22 +689,19 @@ ssh_packet_close_internal(struct ssh *ssh, int do_close)
+@@ -686,22 +694,19 @@ ssh_packet_close_internal(struct ssh *ssh, int do_close)
  	struct session_state *state = ssh->state;
  	u_int mode;
  	struct packet *p;
@@ -1759,7 +1759,7 @@ index 190a579d1..fa6f4c843 100644
  	while ((p = TAILQ_FIRST(&state->outgoing))) {
  		sshbuf_free(p->payload);
  		TAILQ_REMOVE(&state->outgoing, p, next);
-@@ -737,8 +742,18 @@ ssh_packet_close_internal(struct ssh *ssh, int do_close)
+@@ -742,8 +747,18 @@ ssh_packet_close_internal(struct ssh *ssh, int do_close)
  #endif	/* WITH_ZLIB */
  	cipher_free(state->send_context);
  	cipher_free(state->receive_context);
@@ -1778,7 +1778,7 @@ index 190a579d1..fa6f4c843 100644
  		free(ssh->local_ipaddr);
  		ssh->local_ipaddr = NULL;
  		free(ssh->remote_ipaddr);
-@@ -1004,6 +1019,7 @@ ssh_set_newkeys(struct ssh *ssh, int mode)
+@@ -1009,6 +1024,7 @@ ssh_set_newkeys(struct ssh *ssh, int mode)
  		    (unsigned long long)state->p_send.bytes,
  		    (unsigned long long)state->p_send.blocks);
  		kex_free_newkeys(state->newkeys[mode]);
@@ -1786,7 +1786,7 @@ index 190a579d1..fa6f4c843 100644
  		state->newkeys[mode] = NULL;
  	}
  	/* note that both bytes and the seqnr are not reset */
-@@ -2385,6 +2401,72 @@ ssh_packet_get_output(struct ssh *ssh)
+@@ -2397,6 +2413,72 @@ ssh_packet_get_output(struct ssh *ssh)
  	return (void *)ssh->state->output;
  }
  
@@ -1870,7 +1870,7 @@ index 3e8acb2cd..e9aefdaad 100644
 +void	 packet_destroy_all(struct ssh *, int, int);
  #endif				/* PACKET_H */
 diff --git a/session.c b/session.c
-index 0c26448d9..75ff1080e 100644
+index 572487b4a..d36c1b702 100644
 --- a/session.c
 +++ b/session.c
 @@ -139,6 +139,7 @@ static int session_pty_req(struct ssh *, Session *);
@@ -1930,7 +1930,7 @@ index 0c26448d9..75ff1080e 100644
  
  	/* Force a password change */
  	if (s->authctxt->force_pwchange) {
-@@ -1717,6 +1734,9 @@ session_unused(int id)
+@@ -1718,6 +1735,9 @@ session_unused(int id)
  	sessions[id].ttyfd = -1;
  	sessions[id].ptymaster = -1;
  	sessions[id].x11_chanids = NULL;
@@ -1940,7 +1940,7 @@ index 0c26448d9..75ff1080e 100644
  	sessions[id].next_unused = sessions_first_unused;
  	sessions_first_unused = id;
  }
-@@ -1795,6 +1815,19 @@ session_open(Authctxt *authctxt, int chanid)
+@@ -1796,6 +1816,19 @@ session_open(Authctxt *authctxt, int chanid)
  	return 1;
  }
  
@@ -1960,7 +1960,7 @@ index 0c26448d9..75ff1080e 100644
  Session *
  session_by_tty(char *tty)
  {
-@@ -2413,6 +2446,32 @@ session_exit_message(struct ssh *ssh, Session *s, int status)
+@@ -2417,6 +2450,32 @@ session_exit_message(struct ssh *ssh, Session *s, int status)
  		chan_write_failed(ssh, c);
  }
  
@@ -1993,7 +1993,7 @@ index 0c26448d9..75ff1080e 100644
  void
  session_close(struct ssh *ssh, Session *s)
  {
-@@ -2426,6 +2485,10 @@ session_close(struct ssh *ssh, Session *s)
+@@ -2430,6 +2489,10 @@ session_close(struct ssh *ssh, Session *s)
  
  	if (s->ttyfd != -1)
  		session_pty_cleanup(s);
@@ -2004,7 +2004,7 @@ index 0c26448d9..75ff1080e 100644
  	free(s->term);
  	free(s->display);
  	free(s->x11_chanids);
-@@ -2502,14 +2565,14 @@ session_close_by_channel(struct ssh *ssh, int id, int force, void *arg)
+@@ -2506,14 +2569,14 @@ session_close_by_channel(struct ssh *ssh, int id, int force, void *arg)
  }
  
  void
@@ -2021,7 +2021,7 @@ index 0c26448d9..75ff1080e 100644
  			else
  				session_close(ssh, s);
  		}
-@@ -2635,6 +2698,15 @@ do_authenticated2(struct ssh *ssh, Authctxt *authctxt)
+@@ -2639,6 +2702,15 @@ do_authenticated2(struct ssh *ssh, Authctxt *authctxt)
  	server_loop2(ssh, authctxt);
  }
  
@@ -2037,7 +2037,7 @@ index 0c26448d9..75ff1080e 100644
  void
  do_cleanup(struct ssh *ssh, Authctxt *authctxt)
  {
-@@ -2698,7 +2770,7 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
+@@ -2702,7 +2774,7 @@ do_cleanup(struct ssh *ssh, Authctxt *authctxt)
  	 * or if running in monitor.
  	 */
  	if (mm_is_monitor())
@@ -2078,7 +2078,7 @@ index 344a1ddf9..a41c6efcd 100644
  void	 session_close(struct ssh *, Session *);
  void	 do_setusercontext(struct passwd *);
 diff --git a/sshd-session.c b/sshd-session.c
-index 23b80eb01..d8972fbd5 100644
+index 6f07c843a..fa1192c09 100644
 --- a/sshd-session.c
 +++ b/sshd-session.c
 @@ -181,8 +181,8 @@ struct include_list includes = TAILQ_HEAD_INITIALIZER(includes);
@@ -2221,7 +2221,7 @@ index 23b80eb01..d8972fbd5 100644
  
  	reseed_prngs();
  
-@@ -1333,6 +1402,9 @@ main(int ac, char **av)
+@@ -1334,6 +1403,9 @@ main(int ac, char **av)
  	do_authenticated(ssh, authctxt);
  
  	/* The connection has been terminated. */
@@ -2231,7 +2231,7 @@ index 23b80eb01..d8972fbd5 100644
  	ssh_packet_get_bytes(ssh, &ibytes, &obytes);
  	verbose("Transferred: sent %llu, received %llu bytes",
  	    (unsigned long long)obytes, (unsigned long long)ibytes);
-@@ -1378,6 +1450,16 @@ sshd_hostkey_sign(struct ssh *ssh, struct sshkey *privkey,
+@@ -1379,6 +1451,16 @@ sshd_hostkey_sign(struct ssh *ssh, struct sshkey *privkey,
  void
  cleanup_exit(int i)
  {
@@ -2248,7 +2248,7 @@ index 23b80eb01..d8972fbd5 100644
  	if (the_active_state != NULL && the_authctxt != NULL) {
  		do_cleanup(the_active_state, the_authctxt);
  		if (privsep_is_preauth &&
-@@ -1392,7 +1474,9 @@ cleanup_exit(int i)
+@@ -1393,7 +1475,9 @@ cleanup_exit(int i)
  	}
  #ifdef SSH_AUDIT_EVENTS
  	/* done after do_cleanup so it can cancel the PAM auth 'thread' */
@@ -2260,7 +2260,7 @@ index 23b80eb01..d8972fbd5 100644
  #endif
  	/* Override default fatal exit value when auth was attempted */
 diff --git a/sshd.c b/sshd.c
-index 552435817..22ad4dff0 100644
+index f66e1f86f..6b9847f67 100644
 --- a/sshd.c
 +++ b/sshd.c
 @@ -206,6 +206,15 @@ close_listen_socks(void)
@@ -2288,5 +2288,5 @@ index 552435817..22ad4dff0 100644
  			if (options.pid_file != NULL)
  				unlink(options.pid_file);
 -- 
-2.53.0
+2.55.0
 

diff --git a/0036-openssh-7.1p2-audit-race-condition.patch b/0036-openssh-7.1p2-audit-race-condition.patch
index 3bd6594..a2bec61 100644
--- a/0036-openssh-7.1p2-audit-race-condition.patch
+++ b/0036-openssh-7.1p2-audit-race-condition.patch
@@ -1,4 +1,4 @@
-From d18c0e8c12110d7a46bcbfb67cb6e28e7ebcecf1 Mon Sep 17 00:00:00 2001
+From 89eacbbddf3f04349b37f9cfc75f0080f9d82b5d Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 36/54] openssh-7.1p2-audit-race-condition
@@ -11,10 +11,10 @@ Subject: [PATCH 36/54] openssh-7.1p2-audit-race-condition
  3 files changed, 102 insertions(+), 7 deletions(-)
 
 diff --git a/monitor_wrap.c b/monitor_wrap.c
-index ef419b743..fa3718924 100644
+index 7ccf8729c..cd70a4e12 100644
 --- a/monitor_wrap.c
 +++ b/monitor_wrap.c
-@@ -1446,4 +1446,50 @@ mm_audit_destroy_sensitive_data(struct ssh *ssh, const char *fp, pid_t pid, uid_
+@@ -1394,4 +1394,50 @@ mm_audit_destroy_sensitive_data(struct ssh *ssh, const char *fp, pid_t pid, uid_
  	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_SERVER_KEY_FREE, m);
  	sshbuf_free(m);
  }
@@ -66,7 +66,7 @@ index ef419b743..fa3718924 100644
 +}
  #endif /* SSH_AUDIT_EVENTS */
 diff --git a/monitor_wrap.h b/monitor_wrap.h
-index 9856968f9..0865f59cb 100644
+index 7f846f085..41e4b79fe 100644
 --- a/monitor_wrap.h
 +++ b/monitor_wrap.h
 @@ -98,6 +98,8 @@ void mm_audit_unsupported_body(struct ssh *, int);
@@ -79,7 +79,7 @@ index 9856968f9..0865f59cb 100644
  
  struct Session;
 diff --git a/session.c b/session.c
-index 75ff1080e..1a7f5ca7a 100644
+index d36c1b702..bd8dc808c 100644
 --- a/session.c
 +++ b/session.c
 @@ -161,6 +161,10 @@ static Session *sessions = NULL;
@@ -200,5 +200,5 @@ index 75ff1080e..1a7f5ca7a 100644
  	if (s->authctxt->force_pwchange) {
  		do_setusercontext(pw);
 -- 
-2.53.0
+2.55.0
 

diff --git a/0037-openssh-9.0p1-audit-log.patch b/0037-openssh-9.0p1-audit-log.patch
index 4e8f27e..19f1818 100644
--- a/0037-openssh-9.0p1-audit-log.patch
+++ b/0037-openssh-9.0p1-audit-log.patch
@@ -1,4 +1,4 @@
-From 8a4843f8726028ca6b10285698dccea852632592 Mon Sep 17 00:00:00 2001
+From a5dc196b4c6d2462e87f5cb825e1b630b1217d06 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 37/54] openssh-9.0p1-audit-log
@@ -262,5 +262,5 @@ index 45d66ccff..05ac132cf 100644
  void	audit_unsupported(struct ssh *, int);
  void	audit_kex(struct ssh *, int, char *, char *, char *, char *);
 -- 
-2.53.0
+2.55.0
 

diff --git a/0038-openssh-7.7p1-fips.patch b/0038-openssh-7.7p1-fips.patch
index 19c1b21..4eb8e2b 100644
--- a/0038-openssh-7.7p1-fips.patch
+++ b/0038-openssh-7.7p1-fips.patch
@@ -1,4 +1,4 @@
-From 55c0d54b91cc39ed6f10542f883dbaad6a3213ca Mon Sep 17 00:00:00 2001
+From eb6be743509407de959ef3a5e95cd76815b48dca Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 28 Aug 2025 14:01:38 +0200
 Subject: [PATCH 38/54] openssh-7.7p1-fips
@@ -9,7 +9,7 @@ Subject: [PATCH 38/54] openssh-7.7p1-fips
  kex-names.c              |  6 +++-
  kex.c                    |  3 +-
  kexgen.c                 | 74 ++++++++++++++++++++++++++++++++--------
- kexgexc.c                |  5 +++
+ kexgexc.c                |  6 ++++
  myproposal.h             | 33 ++++++++++++++++++
  readconf.c               | 21 +++++++++---
  sandbox-seccomp-filter.c |  3 ++
@@ -23,7 +23,7 @@ Subject: [PATCH 38/54] openssh-7.7p1-fips
  sshconnect2.c            | 11 +++++-
  sshd.c                   | 19 +++++++++++
  sshkey.c                 | 37 ++++++++++++++++++++
- 19 files changed, 300 insertions(+), 30 deletions(-)
+ 19 files changed, 301 insertions(+), 30 deletions(-)
 
 diff --git a/dh.c b/dh.c
 index b291750d8..3efe0a69f 100644
@@ -102,7 +102,7 @@ index c6326a39d..e51e292b8 100644
  u_int	 dh_estimate(int);
  void	 dh_set_moduli_file(const char *);
 diff --git a/kex-names.c b/kex-names.c
-index 769a36810..c0761ae8f 100644
+index 3bc38130b..dcb6efc13 100644
 --- a/kex-names.c
 +++ b/kex-names.c
 @@ -32,6 +32,7 @@
@@ -113,7 +113,7 @@ index 769a36810..c0761ae8f 100644
  #include <openssl/evp.h>
  #endif
  
-@@ -207,7 +208,10 @@ kex_names_valid(const char *names)
+@@ -213,7 +214,10 @@ kex_names_valid(const char *names)
  			return 0;
  		}
  		if (kex_alg_by_name(p) == NULL) {
@@ -126,7 +126,7 @@ index 769a36810..c0761ae8f 100644
  			return 0;
  		}
 diff --git a/kex.c b/kex.c
-index 70bebce6a..e1059dec4 100644
+index 3fbf1dd43..abd13a0e2 100644
 --- a/kex.c
 +++ b/kex.c
 @@ -37,6 +37,7 @@
@@ -265,7 +265,7 @@ index a2beb3f10..2f8252488 100644
  	default:
  		r = SSH_ERR_INVALID_ARGUMENT;
 diff --git a/kexgexc.c b/kexgexc.c
-index 1c2194a8f..f9a58b955 100644
+index 1c2194a8f..2763e862a 100644
 --- a/kexgexc.c
 +++ b/kexgexc.c
 @@ -29,6 +29,7 @@
@@ -276,7 +276,7 @@ index 1c2194a8f..f9a58b955 100644
  #include <sys/types.h>
  
  #include <openssl/bn.h>
-@@ -117,6 +118,11 @@ input_kex_dh_gex_group(int type, u_int32
+@@ -116,6 +117,11 @@ input_kex_dh_gex_group(int type, uint32_t seq, struct ssh *ssh)
  	}
  	p = g = NULL; /* belong to kex->dh now */
  
@@ -340,7 +340,7 @@ index d992d8b12..6433e0820 100644
  #define	SSH_ALLOWED_CA_SIGALGS	\
  	"ssh-ed25519," \
 diff --git a/readconf.c b/readconf.c
-index 9004076e4..058e53cf1 100644
+index 1b7d4ae4e..e971ca5c4 100644
 --- a/readconf.c
 +++ b/readconf.c
 @@ -38,6 +38,12 @@
@@ -379,7 +379,7 @@ index 9004076e4..058e53cf1 100644
  	do { \
  		if ((r = kex_assemble_names(&options->what, \
 diff --git a/sandbox-seccomp-filter.c b/sandbox-seccomp-filter.c
-index e21048f3a..25c9df42a 100644
+index b65413c95..17e1cea72 100644
 --- a/sandbox-seccomp-filter.c
 +++ b/sandbox-seccomp-filter.c
 @@ -258,6 +258,9 @@ static const struct sock_filter preauth_insns[] = {
@@ -393,7 +393,7 @@ index e21048f3a..25c9df42a 100644
  	SC_DENY(__NR_openat, EACCES),
  #endif
 diff --git a/servconf.c b/servconf.c
-index 8045756fb..6c1ba77f6 100644
+index 8c1e9c253..bee75d486 100644
 --- a/servconf.c
 +++ b/servconf.c
 @@ -38,7 +38,15 @@
@@ -412,7 +412,7 @@ index 8045756fb..6c1ba77f6 100644
  
  #include "xmalloc.h"
  #include "ssh.h"
-@@ -242,11 +250,16 @@ assemble_algorithms(ServerOptions *o)
+@@ -213,11 +221,16 @@ assemble_algorithms(ServerOptions *o)
  	all_key = sshkey_alg_list(0, 0, 1, ',');
  	all_sig = sshkey_alg_list(0, 1, 1, ',');
  	/* remove unsupported algos from default lists */
@@ -485,7 +485,7 @@ index a894e23c9..329dc9da0 100644
  
  typedef struct {
 diff --git a/ssh-keygen.c b/ssh-keygen.c
-index 584d5a899..12430e6a4 100644
+index a62e5dc4a..eb880dbc0 100644
 --- a/ssh-keygen.c
 +++ b/ssh-keygen.c
 @@ -22,6 +22,7 @@
@@ -524,7 +524,7 @@ index 584d5a899..12430e6a4 100644
  	else {
  		switch (sshkey_type_from_shortname(key_type_name)) {
  #ifdef OPENSSL_HAS_ECC
-@@ -1057,9 +1063,17 @@ do_gen_all_hostkeys(struct passwd *pw)
+@@ -1065,9 +1071,17 @@ do_gen_all_hostkeys(struct passwd *pw)
  			first = 1;
  			printf("%s: generating new host keys: ", __progname);
  		}
@@ -543,7 +543,7 @@ index 584d5a899..12430e6a4 100644
  		if ((fd = mkstemp(prv_tmp)) == -1) {
  			error("Could not save your private key in %s: %s",
  			    prv_tmp, strerror(errno));
-@@ -3768,7 +3782,7 @@ main(int argc, char **argv)
+@@ -3776,7 +3790,7 @@ main(int argc, char **argv)
  	}
  
  	if (key_type_name == NULL)
@@ -553,7 +553,7 @@ index 584d5a899..12430e6a4 100644
  	type = sshkey_type_from_shortname(key_type_name);
  	type_bits_valid(type, key_type_name, &bits);
 diff --git a/ssh-keyscan.c b/ssh-keyscan.c
-index bb3ee462d..3cea1daac 100644
+index c455ef532..1f15f3077 100644
 --- a/ssh-keyscan.c
 +++ b/ssh-keyscan.c
 @@ -21,6 +21,7 @@
@@ -564,7 +564,7 @@ index bb3ee462d..3cea1daac 100644
  
  #include <errno.h>
  #include <limits.h>
-@@ -234,6 +235,14 @@ keygrab_ssh2(con *c)
+@@ -235,6 +236,14 @@ keygrab_ssh2(con *c)
  	char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT };
  	int r;
  
@@ -609,7 +609,7 @@ index ccadb14ca..5af39bb39 100644
  		goto out;
  	}
 diff --git a/ssh.c b/ssh.c
-index bc3be8f8e..e7bdba327 100644
+index 07bb1e38f..4dee7849f 100644
 --- a/ssh.c
 +++ b/ssh.c
 @@ -71,6 +71,7 @@
@@ -620,7 +620,7 @@ index bc3be8f8e..e7bdba327 100644
  #include "openbsd-compat/openssl-compat.h"
  
  #include "xmalloc.h"
-@@ -1631,6 +1632,10 @@ main(int ac, char **av)
+@@ -1613,6 +1614,10 @@ main(int ac, char **av)
  		exit(0);
  	}
  
@@ -632,7 +632,7 @@ index bc3be8f8e..e7bdba327 100644
  	if (options.sk_provider != NULL && *options.sk_provider == '$' &&
  	    strlen(options.sk_provider) > 1) {
 diff --git a/sshconnect2.c b/sshconnect2.c
-index 31bbc0ed8..2ddd89d5c 100644
+index e1d4388ad..ca9dc0e8a 100644
 --- a/sshconnect2.c
 +++ b/sshconnect2.c
 @@ -45,6 +45,10 @@
@@ -646,7 +646,7 @@ index 31bbc0ed8..2ddd89d5c 100644
  #include "xmalloc.h"
  #include "ssh.h"
  #include "ssh2.h"
-@@ -263,6 +267,9 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+@@ -265,6 +269,9 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
  
  #if defined(GSSAPI) && defined(WITH_OPENSSL)
  	if (options.gss_keyex) {
@@ -656,7 +656,7 @@ index 31bbc0ed8..2ddd89d5c 100644
  		/* Add the GSSAPI mechanisms currently supported on this
  		 * client to the key exchange algorithm proposal */
  		orig = myproposal[PROPOSAL_KEX_ALGS];
-@@ -282,7 +289,9 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+@@ -284,7 +291,9 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
  		}
  
  		gss = ssh_gssapi_client_mechanisms(gss_host,
@@ -668,7 +668,7 @@ index 31bbc0ed8..2ddd89d5c 100644
  			debug("Offering GSSAPI proposal: %s", gss);
  			xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
 diff --git a/sshd.c b/sshd.c
-index 22ad4dff0..2865619c7 100644
+index 6b9847f67..ae26b10b3 100644
 --- a/sshd.c
 +++ b/sshd.c
 @@ -43,6 +43,7 @@
@@ -693,8 +693,8 @@ index 22ad4dff0..2865619c7 100644
  #ifdef HAVE_SECUREWARE
  #include <sys/security.h>
  #include <prot.h>
-@@ -1604,6 +1612,13 @@ main(int ac, char **av)
- 		    &key, NULL)) != 0 && r != SSH_ERR_SYSTEM_ERROR)
+@@ -1605,6 +1613,13 @@ main(int ac, char **av)
+ 		    r != SSH_ERR_SYSTEM_ERROR && !have_agent)
  			do_log2_r(r, ll, "Unable to load host key \"%s\"",
  			    options.host_key_files[i]);
 +		if (FIPS_mode() && key != NULL && (sshkey_type_plain(key->type) == KEY_ED25519_SK
@@ -707,7 +707,7 @@ index 22ad4dff0..2865619c7 100644
  		if (sshkey_is_sk(key) &&
  		    key->sk_flags & SSH_SK_USER_PRESENCE_REQD) {
  			debug("host key %s requires user presence, ignoring",
-@@ -1827,6 +1842,10 @@ main(int ac, char **av)
+@@ -1829,6 +1844,10 @@ main(int ac, char **av)
  	/* Reinitialize the log (because of the fork above). */
  	log_init(__progname, options.log_level, options.log_facility, log_stderr);
  
@@ -719,7 +719,7 @@ index 22ad4dff0..2865619c7 100644
  	 * Chdir to the root directory so that the current disk can be
  	 * unmounted if desired.
 diff --git a/sshkey.c b/sshkey.c
-index ccc35dbf5..82e55509d 100644
+index 6bb8feaa9..e3fb29e70 100644
 --- a/sshkey.c
 +++ b/sshkey.c
 @@ -36,6 +36,7 @@
@@ -738,7 +738,7 @@ index ccc35dbf5..82e55509d 100644
  #include "ssh-sk.h"
  #include "ssh-pkcs11.h"
  
-@@ -399,6 +401,18 @@ sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep)
+@@ -407,6 +409,18 @@ sshkey_alg_list(int certs_only, int plain_only, int include_sigonly, char sep)
  		impl = keyimpls[i];
  		if (impl->name == NULL || impl->type == KEY_NULL)
  			continue;
@@ -757,7 +757,7 @@ index ccc35dbf5..82e55509d 100644
  		if (!include_sigonly && impl->sigonly)
  			continue;
  		if ((certs_only && !impl->cert) || (plain_only && impl->cert))
-@@ -1431,6 +1445,20 @@ sshkey_read(struct sshkey *ret, char **cpp)
+@@ -1443,6 +1457,20 @@ sshkey_read(struct sshkey *ret, char **cpp)
  		return SSH_ERR_EC_CURVE_MISMATCH;
  	}
  
@@ -778,7 +778,7 @@ index ccc35dbf5..82e55509d 100644
  	/* Fill in ret from parsed key */
  	sshkey_free_contents(ret);
  	*ret = *k;
-@@ -2264,6 +2292,11 @@ sshkey_sign(struct sshkey *key,
+@@ -2276,6 +2304,11 @@ sshkey_sign(struct sshkey *key,
  		*lenp = 0;
  	if (datalen > SSH_KEY_MAX_SIGN_DATA_SIZE)
  		return SSH_ERR_INVALID_ARGUMENT;
@@ -790,7 +790,7 @@ index ccc35dbf5..82e55509d 100644
  	if ((impl = sshkey_impl_from_key(key)) == NULL)
  		return SSH_ERR_KEY_TYPE_UNKNOWN;
  	if ((r = sshkey_unshield_private(key)) != 0)
-@@ -2303,6 +2336,10 @@ sshkey_verify(const struct sshkey *key,
+@@ -2315,6 +2348,10 @@ sshkey_verify(const struct sshkey *key,
  		*detailsp = NULL;
  	if (siglen == 0 || dlen > SSH_KEY_MAX_SIGN_DATA_SIZE)
  		return SSH_ERR_INVALID_ARGUMENT;
@@ -802,5 +802,5 @@ index ccc35dbf5..82e55509d 100644
  		return SSH_ERR_KEY_TYPE_UNKNOWN;
  	return impl->funcs->verify(key, sig, siglen, data, dlen,
 -- 
-2.53.0
+2.55.0
 

diff --git a/0039-openssh-8.7p1-negotiate-supported-algs.patch b/0039-openssh-8.7p1-negotiate-supported-algs.patch
index d00bed4..3b65c25 100644
--- a/0039-openssh-8.7p1-negotiate-supported-algs.patch
+++ b/0039-openssh-8.7p1-negotiate-supported-algs.patch
@@ -1,4 +1,4 @@
-From bfbdaf0b1355e06be5c40dae3986d4ddde50ac08 Mon Sep 17 00:00:00 2001
+From caab11e7840ce22b21e88fdd6fac693cb897c0a8 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 39/54] openssh-8.7p1-negotiate-supported-algs
@@ -7,12 +7,13 @@ Subject: [PATCH 39/54] openssh-8.7p1-negotiate-supported-algs
 # upstream MR:
 # https://github.com/openssh/openssh-portable/pull/323
 ---
- regress/hostkey-agent.sh | 32 +++++++++++++++++++++++++-------
- sshconnect2.c            | 17 +++++++++++++++--
- 2 files changed, 40 insertions(+), 9 deletions(-)
+ regress/hostkey-agent.sh      | 32 +++++++++++++++++++++++++-------
+ regress/knownhosts-command.sh |  8 ++++++--
+ sshconnect2.c                 | 17 +++++++++++++++--
+ 3 files changed, 46 insertions(+), 11 deletions(-)
 
 diff --git a/regress/hostkey-agent.sh b/regress/hostkey-agent.sh
-index 28dcfe170..b9e716dcd 100644
+index 0e7962e3c..251d5258e 100644
 --- a/regress/hostkey-agent.sh
 +++ b/regress/hostkey-agent.sh
 @@ -17,8 +17,21 @@ trace "make CA key"
@@ -86,12 +87,37 @@ index 28dcfe170..b9e716dcd 100644
  	verbose "cert type $k"
  	opts="-oHostKeyAlgorithms=$k -F $OBJ/ssh_proxy"
  	SSH_CONNECTION=`${SSH} $opts host 'echo $SSH_CONNECTION'`
+diff --git a/regress/knownhosts-command.sh b/regress/knownhosts-command.sh
+index 58004b80e..036a7c2b9 100644
+--- a/regress/knownhosts-command.sh
++++ b/regress/knownhosts-command.sh
+@@ -38,8 +38,12 @@ _EOF
+ chmod a+x $OBJ/knownhosts_command
+ ${SSH} -F $OBJ/ssh_proxy x true && fail "ssh connect succeeded with bad exit"
+ 
++PUBKEY_ACCEPTED_ALGOS=`$SSH -G "example.com" | \
++    grep -i "PubkeyAcceptedAlgorithms" | cut -d ' ' -f2- | tr "," "|"`
++SSH_ACCEPTED_HOSTKEY_TYPES=`echo "$SSH_HOSTKEY_TYPES" | tr ' ' '\n' | egrep "$PUBKEY_ACCEPTED_ALGOS"`
++
+ cp $OBJ/sshd_proxy $OBJ/sshd_proxy.bak
+-for keytype in ${SSH_HOSTKEY_TYPES} ; do
++for keytype in ${SSH_ACCEPTED_HOSTKEY_TYPES} ; do
+ 	grep -vi HostKeyAlgorithms $OBJ/sshd_proxy.bak > $OBJ/sshd_proxy
+ 	echo "HostKeyAlgorithms=+$keytype" >> $OBJ/sshd_proxy
+ 	algs=$keytype
+@@ -53,5 +57,5 @@ test "x\$3" = "x$LOGNAME" || die "wrong username \$3 (expected $LOGNAME)"
+ grep -- "\$1.*\$2" $OBJ/known_hosts
+ _EOF
+ 	${SSH} -F $OBJ/ssh_proxy -oHostKeyAlgorithms=$algs x true ||
+-	    fail "ssh connect failed for keytype $x"
++	    fail "ssh connect failed for keytype $keytype"
+ done
 diff --git a/sshconnect2.c b/sshconnect2.c
-index 2ddd89d5c..31f51be62 100644
+index ca9dc0e8a..2dc08b8a1 100644
 --- a/sshconnect2.c
 +++ b/sshconnect2.c
-@@ -224,7 +224,7 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
-     const struct ssh_conn_info *cinfo)
+@@ -224,7 +224,7 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
+     u_short port, const struct ssh_conn_info *cinfo)
  {
  	char *myproposal[PROPOSAL_MAX];
 -	char *all_key, *hkalgs = NULL;
@@ -99,9 +125,9 @@ index 2ddd89d5c..31f51be62 100644
  	int r, use_known_hosts_order = 0;
  
  #if defined(GSSAPI) && defined(WITH_OPENSSL)
-@@ -260,10 +260,22 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
- 	if (use_known_hosts_order)
- 		hkalgs = order_hostkeyalgs(host, hostaddr, port, cinfo);
+@@ -262,10 +262,22 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
+ 		    port, cinfo);
+ 	}
  
 +	filtered_algs = hkalgs ? match_filter_allowlist(hkalgs, options.pubkey_accepted_algos)
 +		               : match_filter_allowlist(options.hostkeyalgorithms,
@@ -123,7 +149,7 @@ index 2ddd89d5c..31f51be62 100644
  
  #if defined(GSSAPI) && defined(WITH_OPENSSL)
  	if (options.gss_keyex) {
-@@ -307,6 +319,7 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
+@@ -309,6 +321,7 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
  #endif
  
  	free(hkalgs);
@@ -132,5 +158,5 @@ index 2ddd89d5c..31f51be62 100644
  	/* start key exchange */
  	if ((r = kex_setup(ssh, myproposal)) != 0)
 -- 
-2.53.0
+2.55.0
 

diff --git a/0040-openssh-9.0p1-evp-fips-kex.patch b/0040-openssh-9.0p1-evp-fips-kex.patch
index 66fd4e6..e5ae16d 100644
--- a/0040-openssh-9.0p1-evp-fips-kex.patch
+++ b/0040-openssh-9.0p1-evp-fips-kex.patch
@@ -1,4 +1,4 @@
-From fc166604790409eefd5f550c71c232be3c18d94d Mon Sep 17 00:00:00 2001
+From 16878783113f7c49eaec1db60d2667f2cd36223e Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 40/54] openssh-9.0p1-evp-fips-kex
@@ -140,10 +140,10 @@ index 3efe0a69f..bf324309c 100644
  
  DH *
 diff --git a/kex.c b/kex.c
-index e1059dec4..1eb3ca9d3 100644
+index abd13a0e2..a51e26398 100644
 --- a/kex.c
 +++ b/kex.c
-@@ -1619,3 +1619,142 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
+@@ -1624,3 +1624,142 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms,
  	return r;
  }
  
@@ -287,7 +287,7 @@ index e1059dec4..1eb3ca9d3 100644
 +}
 +#endif /* WITH_OPENSSL */
 diff --git a/kex.h b/kex.h
-index 0a0cb1562..ff41324e9 100644
+index 692e28363..d22f6deb3 100644
 --- a/kex.h
 +++ b/kex.h
 @@ -37,6 +37,9 @@
@@ -300,7 +300,7 @@ index 0a0cb1562..ff41324e9 100644
  # ifdef OPENSSL_HAS_ECC
  #  include <openssl/ec.h>
  # else /* OPENSSL_HAS_ECC */
-@@ -317,6 +320,9 @@ int	kexc25519_shared_key_ext(const u_char key[CURVE25519_SIZE],
+@@ -318,6 +321,9 @@ int	kexc25519_shared_key_ext(const u_char key[CURVE25519_SIZE],
      const u_char pub[CURVE25519_SIZE], struct sshbuf *out, int)
  	__attribute__((__bounded__(__minbytes__, 1, CURVE25519_SIZE)))
  	__attribute__((__bounded__(__minbytes__, 2, CURVE25519_SIZE)));
@@ -611,5 +611,5 @@ index 6a9058cdc..b661e0705 100644
  	return r;
  }
 -- 
-2.53.0
+2.55.0
 

diff --git a/0041-openssh-8.7p1-nohostsha1proof.patch b/0041-openssh-8.7p1-nohostsha1proof.patch
index 0b62cc4..a5a6b4d 100644
--- a/0041-openssh-8.7p1-nohostsha1proof.patch
+++ b/0041-openssh-8.7p1-nohostsha1proof.patch
@@ -1,4 +1,4 @@
-From 816d931356b3b72f61929c8d66746b55d96882bc Mon Sep 17 00:00:00 2001
+From 54fa512753bac8db13eed233f5b421e2f733294d Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 41/54] openssh-8.7p1-nohostsha1proof
@@ -65,10 +65,10 @@ index 1a19060fc..2e6db5bf9 100644
  /* #define unused		0x00000020 */
  #define SSH_BUG_DEBUG		0x00000040
 diff --git a/monitor.c b/monitor.c
-index 305883278..bf5a5105b 100644
+index 7dfc7976b..0393c71aa 100644
 --- a/monitor.c
 +++ b/monitor.c
-@@ -755,11 +755,18 @@ int
+@@ -741,11 +741,18 @@ int
  mm_answer_setcompat(struct ssh *ssh, int sock, struct sshbuf *m)
  {
  	int r;
@@ -88,7 +88,7 @@ index 305883278..bf5a5105b 100644
  	compat_set = 1;
  
  	return (0);
-@@ -773,10 +780,12 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -759,10 +766,12 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m)
  	struct sshbuf *sigbuf = NULL;
  	u_char *p = NULL, *signature = NULL;
  	char *alg = NULL;
@@ -103,7 +103,7 @@ index 305883278..bf5a5105b 100644
  
  	debug3_f("entering");
  
-@@ -838,18 +847,30 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -824,18 +833,30 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m)
  	}
  
  	if ((key = get_hostkey_by_index(keyid)) != NULL) {
@@ -152,7 +152,7 @@ index 16c2f2dff..f4700deeb 100644
  	kex_params.proposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = keyname;
  	ASSERT_INT_EQ(ssh_init(&client, 0, &kex_params), 0);
 diff --git a/regress/unittests/sshkey/test_file.c b/regress/unittests/sshkey/test_file.c
-index e412b75d8..5b06bc905 100644
+index 296e90e78..1d30cf22f 100644
 --- a/regress/unittests/sshkey/test_file.c
 +++ b/regress/unittests/sshkey/test_file.c
 @@ -106,6 +106,7 @@ sshkey_file_tests(void)
@@ -173,10 +173,10 @@ index e412b75d8..5b06bc905 100644
  	TEST_START("load RSA cert with SHA512 signature");
  	ASSERT_INT_EQ(sshkey_load_cert(test_data_file("rsa_1_sha512"), &k2), 0);
 diff --git a/regress/unittests/sshkey/test_fuzz.c b/regress/unittests/sshkey/test_fuzz.c
-index d0f47d7cf..ba4d506d5 100644
+index fb178c1e4..6ecb5872d 100644
 --- a/regress/unittests/sshkey/test_fuzz.c
 +++ b/regress/unittests/sshkey/test_fuzz.c
-@@ -273,13 +273,14 @@ sshkey_fuzz_tests(void)
+@@ -317,13 +317,14 @@ sshkey_fuzz_tests(void)
  	TEST_DONE();
  
  #ifdef WITH_OPENSSL
@@ -193,7 +193,7 @@ index d0f47d7cf..ba4d506d5 100644
  	TEST_START("fuzz RSA SHA256 sig");
  	buf = load_file("rsa_1");
 diff --git a/regress/unittests/sshkey/test_sshkey.c b/regress/unittests/sshkey/test_sshkey.c
-index 1701c87c5..36f6fd335 100644
+index ec0ce452c..db3e22277 100644
 --- a/regress/unittests/sshkey/test_sshkey.c
 +++ b/regress/unittests/sshkey/test_sshkey.c
 @@ -59,6 +59,9 @@ build_cert(struct sshbuf *b, struct sshkey *k, const char *type,
@@ -247,7 +247,7 @@ index 1701c87c5..36f6fd335 100644
  	free(sig);
  }
  
-@@ -552,7 +560,7 @@ sshkey_tests(void)
+@@ -650,7 +658,7 @@ sshkey_tests(void)
  	ASSERT_INT_EQ(sshkey_load_public(test_data_file("rsa_1.pub"), &k2,
  	    NULL), 0);
  	k3 = get_private("rsa_1");
@@ -257,7 +257,7 @@ index 1701c87c5..36f6fd335 100644
  	    SSH_ERR_KEY_CERT_INVALID_SIGN_KEY);
  	ASSERT_PTR_EQ(k4, NULL);
 diff --git a/serverloop.c b/serverloop.c
-index 8e63480ec..1087a6c7d 100644
+index 8a6e3db80..e50985e90 100644
 --- a/serverloop.c
 +++ b/serverloop.c
 @@ -76,6 +76,7 @@
@@ -295,10 +295,10 @@ index 5af39bb39..88232c6ed 100644
  			goto out;
  		}
 diff --git a/sshconnect2.c b/sshconnect2.c
-index 31f51be62..eee1f9794 100644
+index 2dc08b8a1..0c82c7782 100644
 --- a/sshconnect2.c
 +++ b/sshconnect2.c
-@@ -1440,6 +1440,14 @@ identity_sign(struct identity *id, u_char **sigp, size_t *lenp,
+@@ -1442,6 +1442,14 @@ identity_sign(struct identity *id, u_char **sigp, size_t *lenp,
  			retried = 1;
  			goto retry_pin;
  		}
@@ -314,10 +314,10 @@ index 31f51be62..eee1f9794 100644
  	}
  
 diff --git a/sshd-session.c b/sshd-session.c
-index d8972fbd5..7064afd7c 100644
+index fa1192c09..0284c8e42 100644
 --- a/sshd-session.c
 +++ b/sshd-session.c
-@@ -1260,6 +1260,27 @@ main(int ac, char **av)
+@@ -1261,6 +1261,27 @@ main(int ac, char **av)
  
  	check_ip_options(ssh);
  
@@ -346,5 +346,5 @@ index d8972fbd5..7064afd7c 100644
  	channel_init_channels(ssh);
  	channel_set_af(ssh, options.address_family);
 -- 
-2.53.0
+2.55.0
 

diff --git a/0042-openssh-9.9p1-separate-keysign.patch b/0042-openssh-9.9p1-separate-keysign.patch
index 1535e23..4d6e262 100644
--- a/0042-openssh-9.9p1-separate-keysign.patch
+++ b/0042-openssh-9.9p1-separate-keysign.patch
@@ -1,4 +1,4 @@
-From f3de328093334a903ee5459340765b7f5441216a Mon Sep 17 00:00:00 2001
+From cdea86618b12a8fbe9a6e57c8b56c15732a08ab0 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 42/54] openssh-9.9p1-separate-keysign
@@ -8,10 +8,10 @@ Subject: [PATCH 42/54] openssh-9.9p1-separate-keysign
  1 file changed, 1 insertion(+), 1 deletion(-)
 
 diff --git a/ssh_config.5 b/ssh_config.5
-index 306fa1d8a..ab98c172f 100644
+index 0447c8c7e..67d52d874 100644
 --- a/ssh_config.5
 +++ b/ssh_config.5
-@@ -797,7 +797,7 @@ or
+@@ -834,7 +834,7 @@ or
  This option should be placed in the non-hostspecific section.
  See
  .Xr ssh-keysign 8
@@ -21,5 +21,5 @@ index 306fa1d8a..ab98c172f 100644
  Sets the escape character (default:
  .Ql ~ ) .
 -- 
-2.53.0
+2.55.0
 

diff --git a/0043-openssh-9.9p1-openssl-mlkem.patch b/0043-openssh-9.9p1-openssl-mlkem.patch
index 602d7be..997a3fa 100644
--- a/0043-openssh-9.9p1-openssl-mlkem.patch
+++ b/0043-openssh-9.9p1-openssl-mlkem.patch
@@ -1,15 +1,15 @@
-From 1d53d34290defbbb3b6daa967c74b8f00282dc7e Mon Sep 17 00:00:00 2001
+From e82d981cb2d297bfb2097ee4c54b4b79af80561d Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Thu, 15 May 2025 13:43:29 +0200
 Subject: [PATCH 43/54] openssh-9.9p1-openssl-mlkem
 
 ---
  kex-names.c         |  23 +++-
- kexmlkem768x25519.c | 291 ++++++++++++++++++++++++++++++++++++++++++++
- 2 files changed, 313 insertions(+), 1 deletion(-)
+ kexmlkem768x25519.c | 292 ++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 314 insertions(+), 1 deletion(-)
 
 diff --git a/kex-names.c b/kex-names.c
-index c0761ae8f..882f2905b 100644
+index dcb6efc13..6cc8ab47c 100644
 --- a/kex-names.c
 +++ b/kex-names.c
 @@ -108,6 +108,19 @@ static const struct kexalg gss_kexalgs[] = {
@@ -58,13 +58,13 @@ index c0761ae8f..882f2905b 100644
  		if (strcmp(k->name, name) == 0)
  			return k;
 diff --git a/kexmlkem768x25519.c b/kexmlkem768x25519.c
-index 2585d1db3..ab6167221 100644
+index e2c2c0a86..36658120f 100644
 --- a/kexmlkem768x25519.c
 +++ b/kexmlkem768x25519.c
-@@ -44,10 +44,127 @@
+@@ -45,9 +45,127 @@
+ 
  #ifdef USE_MLKEM768X25519
  
- #include "libcrux_mlkem768_sha3.h"
 +#include <openssl/err.h>
 +#include <openssl/evp.h>
 +#include <stdio.h>
@@ -181,16 +181,16 @@ index 2585d1db3..ab6167221 100644
 +
 +    return r;
 +}
- 
++
  int
  kex_kem_mlkem768x25519_keypair(struct kex *kex)
  {
 +#if 0
  	struct sshbuf *buf = NULL;
- 	u_char rnd[LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN], *cp = NULL;
+ 	u_char *cp = NULL;
  	size_t need;
-@@ -82,6 +199,36 @@ kex_kem_mlkem768x25519_keypair(struct kex *kex)
- 	explicit_bzero(rnd, sizeof(rnd));
+@@ -78,6 +196,36 @@ kex_kem_mlkem768x25519_keypair(struct kex *kex)
+  out:
  	sshbuf_free(buf);
  	return r;
 +#else
@@ -226,7 +226,7 @@ index 2585d1db3..ab6167221 100644
  }
  
  int
-@@ -89,6 +236,7 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
+@@ -85,6 +233,7 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
     const struct sshbuf *client_blob, struct sshbuf **server_blobp,
     struct sshbuf **shared_secretp)
  {
@@ -234,7 +234,7 @@ index 2585d1db3..ab6167221 100644
  	struct sshbuf *server_blob = NULL;
  	struct sshbuf *buf = NULL;
  	const u_char *client_pub;
-@@ -181,12 +329,97 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
+@@ -170,12 +319,97 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
  	sshbuf_free(server_blob);
  	sshbuf_free(buf);
  	return r;
@@ -277,13 +277,13 @@ index 2585d1db3..ab6167221 100644
 +		r = SSH_ERR_ALLOC_FAIL;
 +		goto out;
 +	}
-+	if (mlkem768_encap_secret(client_pub, enc.snd, enc.fst.value) != 0)
++	if (mlkem768_encap_secret(client_pub, enc.snd.data, enc.fst.data) != 0)
 +		goto out;
 +
 +	/* generate ECDH key pair, store server pubkey after ciphertext */
 +	kexc25519_keygen(server_key, server_pub);
-+	if ((r = sshbuf_put(buf, enc.snd, sizeof(enc.snd))) != 0 ||
-+	    (r = sshbuf_put(server_blob, enc.fst.value, sizeof(enc.fst.value))) != 0 ||
++	if ((r = sshbuf_put(buf, enc.snd.data, sizeof(enc.snd.data))) != 0 ||
++	    (r = sshbuf_put(server_blob, enc.fst.data, sizeof(enc.fst.data))) != 0 ||
 +	    (r = sshbuf_put(server_blob, server_pub, sizeof(server_pub))) != 0)
 +		goto out;
 +	/* append ECDH shared key */
@@ -295,8 +295,8 @@ index 2585d1db3..ab6167221 100644
 +#ifdef DEBUG_KEXECDH
 +	dump_digest("server public key 25519:", server_pub, CURVE25519_SIZE);
 +	dump_digest("server cipher text:",
-+	    enc.fst.value, sizeof(enc.fst.value));
-+	dump_digest("server kem key:", enc.snd, sizeof(enc.snd));
++	    enc.fst.data, sizeof(enc.fst.data));
++	dump_digest("server kem key:", enc.snd.data, sizeof(enc.snd.data));
 +	dump_digest("concatenation of KEM key and ECDH shared key:",
 +	    sshbuf_ptr(buf), sshbuf_len(buf));
 +#endif
@@ -330,10 +330,10 @@ index 2585d1db3..ab6167221 100644
  {
 +#if 0
  	struct sshbuf *buf = NULL;
- 	u_char mlkem_key[crypto_kem_mlkem768_BYTES];
+ 	u_char shared_secret[MLKEM768_BYTES];
  	const u_char *ciphertext, *server_pub;
-@@ -254,6 +487,64 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
- 	explicit_bzero(mlkem_key, sizeof(mlkem_key));
+@@ -236,6 +470,64 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
+ 	explicit_bzero(shared_secret, sizeof(shared_secret));
  	sshbuf_free(buf);
  	return r;
 +#else
@@ -398,5 +398,5 @@ index 2585d1db3..ab6167221 100644
  #else /* USE_MLKEM768X25519 */
  int
 -- 
-2.53.0
+2.55.0
 

diff --git a/0044-openssh-9.9p2-error_processing.patch b/0044-openssh-9.9p2-error_processing.patch
index 35df34b..d5c5110 100644
--- a/0044-openssh-9.9p2-error_processing.patch
+++ b/0044-openssh-9.9p2-error_processing.patch
@@ -1,4 +1,4 @@
-From 04ec0e34a4d4f867cad32ed8543b32e4de458f8f Mon Sep 17 00:00:00 2001
+From 4223bd1e9bd103159b3e934d6060958c1d28548b Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Fri, 16 May 2025 14:53:54 +0200
 Subject: [PATCH 44/54] openssh-9.9p2-error_processing
@@ -9,10 +9,10 @@ Subject: [PATCH 44/54] openssh-9.9p2-error_processing
  1 file changed, 2 insertions(+)
 
 diff --git a/ssh-agent.c b/ssh-agent.c
-index c73abd1d0..7773a4b07 100644
+index 5fc73d697..17b2a96c0 100644
 --- a/ssh-agent.c
 +++ b/ssh-agent.c
-@@ -1352,6 +1352,8 @@ process_add_identity(SocketEntry *e)
+@@ -1359,6 +1359,8 @@ process_add_identity(SocketEntry *e)
  	if ((r = sshkey_private_deserialize(e->request, &k)) != 0 ||
  	    k == NULL ||
  	    (r = sshbuf_get_cstring(e->request, &comment, NULL)) != 0) {
@@ -22,5 +22,5 @@ index c73abd1d0..7773a4b07 100644
  		goto out;
  	}
 -- 
-2.53.0
+2.55.0
 

diff --git a/0045-Ignore-bad-hostkeys-in-known_hosts-file.patch b/0045-Ignore-bad-hostkeys-in-known_hosts-file.patch
new file mode 100644
index 0000000..61be053
--- /dev/null
+++ b/0045-Ignore-bad-hostkeys-in-known_hosts-file.patch
@@ -0,0 +1,88 @@
+From 0ae45c6f50cd9eefdefa7251a61fdde30464a9a7 Mon Sep 17 00:00:00 2001
+From: Zoltan Fridrich <zfridric@redhat.com>
+Date: Mon, 5 May 2025 11:52:25 +0200
+Subject: [PATCH 45/54] Ignore bad hostkeys in known_hosts file
+
+Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
+
+# https://github.com/openssh/openssh-portable/pull/567
+---
+ hostfile.c | 15 +++++++++++++++
+ hostfile.h |  1 +
+ ssh.c      |  2 ++
+ 3 files changed, 18 insertions(+)
+
+diff --git a/hostfile.c b/hostfile.c
+index 033b29104..4517aa080 100644
+--- a/hostfile.c
++++ b/hostfile.c
+@@ -63,6 +63,14 @@
+ #include "hmac.h"
+ #include "sshbuf.h"
+ 
++static int required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
++
++void
++hostfile_set_minimum_rsa_size(int size)
++{
++	required_rsa_size = size;
++}
++
+ /* XXX hmac is too easy to dictionary attack; use bcrypt? */
+ 
+ static int
+@@ -233,6 +241,7 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
+ 	struct load_callback_ctx *ctx = (struct load_callback_ctx *)_ctx;
+ 	struct hostkeys *hostkeys = ctx->hostkeys;
+ 	struct hostkey_entry *tmp;
++	int r = 0;
+ 
+ 	if (l->status == HKF_STATUS_INVALID) {
+ 		/* XXX make this verbose() in the future */
+@@ -241,6 +250,12 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
+ 		return 0;
+ 	}
+ 
++	if ((r = sshkey_check_rsa_length(l->key, required_rsa_size)) != 0) {
++		debug2_f("%s:%ld: ignoring hostkey: %s",
++		    l->path, l->linenum, ssh_err(r));
++		return 0;
++	}
++
+ 	debug3_f("found %skey type %s in file %s:%lu",
+ 	    l->marker == MRK_NONE ? "" :
+ 	    (l->marker == MRK_CA ? "ca " : "revoked "),
+diff --git a/hostfile.h b/hostfile.h
+index a24a4e329..0e9b1a19a 100644
+--- a/hostfile.h
++++ b/hostfile.h
+@@ -119,5 +119,6 @@ int hostkeys_foreach_file(const char *path, FILE *f,
+     const char *host, const char *ip, u_int options, u_int note);
+ 
+ void hostfile_create_user_ssh_dir(const char *, int);
++void hostfile_set_minimum_rsa_size(int);
+ 
+ #endif
+diff --git a/ssh.c b/ssh.c
+index 4dee7849f..c319d08af 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -97,6 +97,7 @@
+ #include "version.h"
+ #include "ssherr.h"
+ #include "utf8.h"
++#include "hostfile.h"
+ 
+ #ifdef ENABLE_PKCS11
+ #include "ssh-pkcs11.h"
+@@ -1357,6 +1358,7 @@ main(int ac, char **av)
+ 			options.update_hostkeys = 0;
+ 		}
+ 	}
++	hostfile_set_minimum_rsa_size(options.required_rsa_size);
+ 	if (options.connection_attempts <= 0)
+ 		fatal("Invalid number of ConnectionAttempts");
+ 
+-- 
+2.55.0
+

diff --git a/0045-Provide-better-error-for-non-supported-private-keys.patch b/0045-Provide-better-error-for-non-supported-private-keys.patch
deleted file mode 100644
index 320c71e..0000000
--- a/0045-Provide-better-error-for-non-supported-private-keys.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From 1d59bb1f658bab8de931d115959fcb0fe93d27fa Mon Sep 17 00:00:00 2001
-From: Zoltan Fridrich <zfridric@redhat.com>
-Date: Wed, 16 Apr 2025 15:11:59 +0200
-Subject: [PATCH 45/54] Provide better error for non-supported private keys
-
-Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
-
-# https://github.com/openssh/openssh-portable/pull/564
----
- sshkey.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/sshkey.c b/sshkey.c
-index 82e55509d..5225eabf2 100644
---- a/sshkey.c
-+++ b/sshkey.c
-@@ -3554,6 +3554,9 @@ translate_libcrypto_error(unsigned long pem_err)
- 			return SSH_ERR_LIBCRYPTO_ERROR;
- 		}
- 	case ERR_LIB_ASN1:
-+#ifdef ERR_LIB_OSSL_DECODER
-+	case ERR_LIB_OSSL_DECODER:
-+#endif
- 		return SSH_ERR_INVALID_FORMAT;
- 	}
- 	return SSH_ERR_LIBCRYPTO_ERROR;
--- 
-2.53.0
-

diff --git a/0046-Ignore-bad-hostkeys-in-known_hosts-file.patch b/0046-Ignore-bad-hostkeys-in-known_hosts-file.patch
deleted file mode 100644
index 2485e95..0000000
--- a/0046-Ignore-bad-hostkeys-in-known_hosts-file.patch
+++ /dev/null
@@ -1,88 +0,0 @@
-From def442146bbfe1116ca51c0b81a2eebc13a86eeb Mon Sep 17 00:00:00 2001
-From: Zoltan Fridrich <zfridric@redhat.com>
-Date: Mon, 5 May 2025 11:52:25 +0200
-Subject: [PATCH 46/54] Ignore bad hostkeys in known_hosts file
-
-Signed-off-by: Zoltan Fridrich <zfridric@redhat.com>
-
-# https://github.com/openssh/openssh-portable/pull/567
----
- hostfile.c | 15 +++++++++++++++
- hostfile.h |  1 +
- ssh.c      |  2 ++
- 3 files changed, 18 insertions(+)
-
-diff --git a/hostfile.c b/hostfile.c
-index 033b29104..4517aa080 100644
---- a/hostfile.c
-+++ b/hostfile.c
-@@ -63,6 +63,14 @@
- #include "hmac.h"
- #include "sshbuf.h"
- 
-+static int required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
-+
-+void
-+hostfile_set_minimum_rsa_size(int size)
-+{
-+	required_rsa_size = size;
-+}
-+
- /* XXX hmac is too easy to dictionary attack; use bcrypt? */
- 
- static int
-@@ -233,6 +241,7 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
- 	struct load_callback_ctx *ctx = (struct load_callback_ctx *)_ctx;
- 	struct hostkeys *hostkeys = ctx->hostkeys;
- 	struct hostkey_entry *tmp;
-+	int r = 0;
- 
- 	if (l->status == HKF_STATUS_INVALID) {
- 		/* XXX make this verbose() in the future */
-@@ -241,6 +250,12 @@ record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
- 		return 0;
- 	}
- 
-+	if ((r = sshkey_check_rsa_length(l->key, required_rsa_size)) != 0) {
-+		debug2_f("%s:%ld: ignoring hostkey: %s",
-+		    l->path, l->linenum, ssh_err(r));
-+		return 0;
-+	}
-+
- 	debug3_f("found %skey type %s in file %s:%lu",
- 	    l->marker == MRK_NONE ? "" :
- 	    (l->marker == MRK_CA ? "ca " : "revoked "),
-diff --git a/hostfile.h b/hostfile.h
-index a24a4e329..0e9b1a19a 100644
---- a/hostfile.h
-+++ b/hostfile.h
-@@ -119,5 +119,6 @@ int hostkeys_foreach_file(const char *path, FILE *f,
-     const char *host, const char *ip, u_int options, u_int note);
- 
- void hostfile_create_user_ssh_dir(const char *, int);
-+void hostfile_set_minimum_rsa_size(int);
- 
- #endif
-diff --git a/ssh.c b/ssh.c
-index e7bdba327..4a17c0a7d 100644
---- a/ssh.c
-+++ b/ssh.c
-@@ -97,6 +97,7 @@
- #include "version.h"
- #include "ssherr.h"
- #include "utf8.h"
-+#include "hostfile.h"
- 
- #ifdef ENABLE_PKCS11
- #include "ssh-pkcs11.h"
-@@ -1375,6 +1376,7 @@ main(int ac, char **av)
- 			options.update_hostkeys = 0;
- 		}
- 	}
-+	hostfile_set_minimum_rsa_size(options.required_rsa_size);
- 	if (options.connection_attempts <= 0)
- 		fatal("Invalid number of ConnectionAttempts");
- 
--- 
-2.53.0
-

diff --git a/0046-support-authentication-indicators-in-GSSAPI.patch b/0046-support-authentication-indicators-in-GSSAPI.patch
new file mode 100644
index 0000000..251f08a
--- /dev/null
+++ b/0046-support-authentication-indicators-in-GSSAPI.patch
@@ -0,0 +1,458 @@
+From 202fc88521798b649497519cd037ee403d047fc9 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Sun, 12 Apr 2026 12:23:51 +0200
+Subject: [PATCH 46/54] support authentication indicators in GSSAPI
+
+https://github.com/openssh/openssh-portable/pull/500
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ configure.ac    |   1 +
+ gss-serv-krb5.c |  89 ++++++++++++++++++++++++++++++++++----
+ gss-serv.c      | 112 ++++++++++++++++++++++++++++++++++++++++++++++--
+ servconf.c      |  11 +++++
+ servconf.h      |   6 ++-
+ ssh-gss.h       |   7 +++
+ sshd_config.5   |  46 ++++++++++++++++++++
+ 7 files changed, 258 insertions(+), 14 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index caa75f708..4df1a3204 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -5120,6 +5120,7 @@ AC_ARG_WITH([kerberos5],
+ 		AC_CHECK_HEADERS([gssapi.h gssapi/gssapi.h])
+ 		AC_CHECK_HEADERS([gssapi_krb5.h gssapi/gssapi_krb5.h])
+ 		AC_CHECK_HEADERS([gssapi_generic.h gssapi/gssapi_generic.h])
++		AC_CHECK_HEADERS([gssapi_ext.h gssapi/gssapi_ext.h])
+ 
+ 		AC_SEARCH_LIBS([k_hasafs], [kafs], [AC_DEFINE([USE_AFS], [1],
+ 			[Define this if you want to use libkafs' AFS support])])
+diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
+index 723509409..fc7cabc09 100644
+--- a/gss-serv-krb5.c
++++ b/gss-serv-krb5.c
+@@ -43,6 +43,7 @@
+ #include "log.h"
+ #include "misc.h"
+ #include "servconf.h"
++#include "match.h"
+ 
+ #include "ssh-gss.h"
+ 
+@@ -87,6 +88,33 @@ ssh_gssapi_krb5_init(void)
+ 	return 1;
+ }
+ 
++/* Check if any of the indicators in the Kerberos ticket match
++ * one of indicators in the list of allowed/denied rules.
++ * In case of the match, apply the decision from the rule.
++ * In case of no indicator from the ticket matching the rule, deny
++ */
++
++static int
++ssh_gssapi_check_indicators(ssh_gssapi_client *client, int *matched)
++{
++	int ret;
++	u_int i;
++	*matched = -1;
++
++	/* Check indicators */
++	for (i = 0; client->indicators[i] != NULL; i++) {
++		ret = match_pattern_list(client->indicators[i],
++					 options.gss_indicators, 1);
++		/* negative or positive match */
++		if (ret != 0) {
++			*matched = i;
++			return ret;
++		}
++	}
++	/* No rule matched */
++	return 0;
++}
++
+ /* Check if this user is OK to login. This only works with krb5 - other
+  * GSSAPI mechanisms will need their own.
+  * Returns true if the user is OK to log in, otherwise returns 0
+@@ -193,15 +221,15 @@ static int
+ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
+ {
+ 	krb5_principal princ;
+-	int retval;
++	int retval, matched, success;
+ 	const char *errmsg;
+ 	int k5login_exists;
+ 
+ 	if (ssh_gssapi_krb5_init() == 0)
+ 		return 0;
+ 
+-	if ((retval = krb5_parse_name(krb_context, client->exportedname.value,
+-	    &princ))) {
++	retval = krb5_parse_name(krb_context, client->exportedname.value, &princ);
++	if (retval) {
+ 		errmsg = krb5_get_error_message(krb_context, retval);
+ 		logit("krb5_parse_name(): %.100s", errmsg);
+ 		krb5_free_error_message(krb_context, errmsg);
+@@ -216,17 +244,60 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
+ 	if (k5login_exists &&
+ 	    ssh_krb5_kuserok(krb_context, princ, name, k5login_exists)) {
+ 		retval = 1;
+-		logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
+-		    name, (char *)client->displayname.value);
++		errmsg = "krb5_kuserok";
+ 	} else if (ssh_gssapi_krb5_cmdok(princ, client->exportedname.value,
+ 		name, k5login_exists)) {
+ 		retval = 1;
+-		logit("Authorized to %s, krb5 principal %s "
+-		    "(ssh_gssapi_krb5_cmdok)",
+-		    name, (char *)client->displayname.value);
+-	} else
++		errmsg = "ssh_gssapi_krb5_cmdok";
++	} else {
+ 		retval = 0;
++		goto out;
++	}
+ 
++	/* At this point we are good if no indicators were defined */
++	if (options.gss_indicators == NULL) {
++		retval = 1;
++		goto out;
++	}
++
++	/* At this point we have indicators defined in the configuration,
++	 * if clientt did not provide any indicators, we reject */
++	if (!client->indicators) {
++		retval = 0;
++		logit("GSSAPI authentication indicators enforced "
++		      "but indicators not provided by the client. "
++		      "krb5 principal %s denied",
++		      (char *)client->displayname.value);
++		goto out;
++	}
++
++	/* At this point the configuration enforces presence of indicators
++	 * check the match */
++	matched = -1;
++	success = ssh_gssapi_check_indicators(client, &matched);
++
++	switch (success) {
++	case 1:
++		logit("Provided indicator %s allowed by the configuration",
++		      client->indicators[matched]);
++		retval = 1;
++		break;
++	case -1:
++		logit("Provided indicator %s rejected by the configuration",
++		      client->indicators[matched]);
++		retval = 0;
++		break;
++	default:
++		logit("Provided indicators do not match the configuration");
++		retval = 0;
++		break;
++	}
++
++out:
++	if (retval == 1) {
++		logit("Authorized to %s, krb5 principal %s (%s)",
++		      name, (char *)client->displayname.value, errmsg);
++	}
+ 	krb5_free_principal(krb_context, princ);
+ 	return retval;
+ }
+diff --git a/gss-serv.c b/gss-serv.c
+index 6eb7d2163..237a3c40a 100644
+--- a/gss-serv.c
++++ b/gss-serv.c
+@@ -55,7 +55,7 @@ extern ServerOptions options;
+ 
+ static ssh_gssapi_client gssapi_client =
+     { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
+-    GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0};
++    GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL};
+ 
+ ssh_gssapi_mech gssapi_null_mech =
+     { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
+@@ -297,6 +297,99 @@ ssh_gssapi_parse_ename(Gssctxt *ctx, gss_buffer_t ename, gss_buffer_t name)
+ 	return GSS_S_COMPLETE;
+ }
+ 
++
++/* Extract authentication indicators from the Kerberos ticket. Authentication
++ * indicators are GSSAPI name attributes for the name "auth-indicators".
++ * Multiple indicators might be present in the ticket.
++ * Each indicator is an utf8 string. */
++
++#define AUTH_INDICATORS_TAG "auth-indicators"
++#define SSH_GSSAPI_MAX_INDICATORS 64
++
++/* Privileged (called from accept_secure_ctx) */
++static OM_uint32
++ssh_gssapi_getindicators(Gssctxt *ctx, gss_name_t gss_name, ssh_gssapi_client *client)
++{
++	gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET;
++	gss_buffer_desc value = GSS_C_EMPTY_BUFFER;
++	gss_buffer_desc display_value = GSS_C_EMPTY_BUFFER;
++	int is_mechname, authenticated, complete, more;
++	size_t count, i;
++
++	/* always initialize client->indicators */
++	client->indicators = NULL;
++
++	ctx->major = gss_inquire_name(&ctx->minor, gss_name,
++				      &is_mechname, NULL, &attrs);
++	if (ctx->major != GSS_S_COMPLETE)
++		return ctx->major;
++
++	if (attrs == GSS_C_NO_BUFFER_SET) {
++		/* no indicators in the ticket */
++		return GSS_S_COMPLETE;
++	}
++
++	/* client->indicators is NULL terminated */
++	count = 0;
++	client->indicators = xcalloc(count + 1, sizeof(char *));
++
++	for (i = 0; i < attrs->count; i++) {
++		authenticated = 0;
++		complete = 0;
++		more = -1;
++
++		/* skip anything but auth-indicators */
++		if (((sizeof(AUTH_INDICATORS_TAG) - 1) != attrs->elements[i].length) ||
++		    memcmp(AUTH_INDICATORS_TAG, attrs->elements[i].value,
++			   sizeof(AUTH_INDICATORS_TAG) - 1) != 0)
++			continue;
++
++		/* retrieve all indicators */
++		while (more != 0) {
++			value.value = NULL;
++			display_value.value = NULL;
++
++			ctx->major = gss_get_name_attribute(&ctx->minor, gss_name,
++							    &attrs->elements[i],
++							    &authenticated, &complete,
++							    &value, &display_value, &more);
++			if (ctx->major != GSS_S_COMPLETE)
++				goto out;
++
++			if (value.value == NULL || !authenticated)
++				continue;
++
++			if (count >= SSH_GSSAPI_MAX_INDICATORS) {
++				logit("ssh_gssapi_getindicators:"
++				      " too many indicators, truncating at %d",
++				      SSH_GSSAPI_MAX_INDICATORS);
++				goto out;
++			}
++
++			client->indicators[count] = xmalloc(value.length + 1);
++			memcpy(client->indicators[count], value.value, value.length);
++			client->indicators[count][value.length] = '\0';
++			count++;
++
++			/* add NULL terminator */
++			client->indicators = xrecallocarray(client->indicators, count,
++							    count + 1, sizeof(char *));
++		}
++	}
++
++out:
++	if (ctx->major != GSS_S_COMPLETE && client->indicators != NULL) {
++		for (i = 0; i < count; i++)
++			free(client->indicators[i]);
++		free(client->indicators);
++		client->indicators = NULL;
++	}
++	gss_release_buffer(&ctx->minor, &value);
++	gss_release_buffer(&ctx->minor, &display_value);
++	gss_release_buffer_set(&ctx->minor, &attrs);
++	return ctx->major;
++}
++
+ /* Extract the client details from a given context. This can only reliably
+  * be called once for a context */
+ 
+@@ -386,6 +479,12 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ 	}
+ 
+ 	gss_release_buffer(&ctx->minor, &ename);
++	/* Retrieve authentication indicators, if they exist */
++	if ((ctx->major = ssh_gssapi_getindicators(ctx,
++	    ctx->client, client))) {
++		ssh_gssapi_error(ctx);
++		return (ctx->major);
++	}
+ 
+ 	/* We can't copy this structure, so we just move the pointer to it */
+ 	client->creds = ctx->client_creds;
+@@ -453,6 +552,7 @@ int
+ ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
+ {
+ 	OM_uint32 lmin;
++	size_t i;
+ 
+ 	(void) kex; /* used in privilege separation */
+ 
+@@ -471,8 +571,14 @@ ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
+ 			gss_release_buffer(&lmin, &gssapi_client.displayname);
+ 			gss_release_buffer(&lmin, &gssapi_client.exportedname);
+ 			gss_release_cred(&lmin, &gssapi_client.creds);
+-			explicit_bzero(&gssapi_client,
+-			    sizeof(ssh_gssapi_client));
++
++			if (gssapi_client.indicators != NULL) {
++				for (i = 0; gssapi_client.indicators[i] != NULL; i++)
++					free(gssapi_client.indicators[i]);
++				free(gssapi_client.indicators);
++			}
++
++			explicit_bzero(&gssapi_client, sizeof(ssh_gssapi_client));
+ 			return 0;
+ 		}
+ 	else
+diff --git a/servconf.c b/servconf.c
+index bee75d486..90c5839a2 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -458,6 +458,7 @@ fill_default_server_options(ServerOptions *options)
+ 	CLEAR_ON_NONE(options->routing_domain);
+ 	CLEAR_ON_NONE(options->host_key_agent);
+ 	CLEAR_ON_NONE(options->per_source_penalty_exempt);
++	CLEAR_ON_NONE(options->gss_indicators);
+ 
+ 	for (i = 0; i < options->num_host_key_files; i++)
+ 		CLEAR_ON_NONE(options->host_key_files[i]);
+@@ -1490,6 +1491,15 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ 		if (*activep && options->gss_kex_algorithms == NULL)
+ 			options->gss_kex_algorithms = xstrdup(arg);
+ 		break;
++
++	case sGSSAPIIndicators:
++		arg = argv_next(&ac, &av);
++		if (!arg || *arg == '\0')
++			fatal("%s line %d: %s missing argument.",
++			    filename, linenum, keyword);
++		if (options->gss_indicators == NULL)
++			options->gss_indicators = xstrdup(arg);
++		break;
+ #endif /* GSSAPI */
+ 
+ 	case sPasswordAuthentication:
+@@ -4275,6 +4285,7 @@ dump_config(ServerOptions *o)
+ 	dump_cfg_fmtint(sGSSAPIStrictAcceptorCheck, o->gss_strict_acceptor);
+ 	dump_cfg_fmtint(sGSSAPIStoreCredentialsOnRekey, o->gss_store_rekey);
+ 	dump_cfg_string(sGSSAPIKexAlgorithms, o->gss_kex_algorithms);
++	dump_cfg_string(sGSSAPIIndicators, o->gss_indicators);
+ #endif
+ 	dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
+ 	dump_cfg_fmtint(sKbdInteractiveAuthentication,
+diff --git a/servconf.h b/servconf.h
+index 7e809cfff..a8e90e5a9 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -326,7 +326,8 @@ SSHCONF_INTFLAG(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_GLOBAL, 1
+ SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
+-SSHCONF_INTFLAG(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH)
++SSHCONF_INTFLAG(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL, SSHCFG_COPY_MATCH)
+ #else /* GSSAPI */
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
+@@ -336,7 +337,8 @@ SSHCONF_UNSUPPORTED_INT(gss_strict_acceptor, GSSAPIStrictAcceptorCheck, SSHCFG_G
+ SSHCONF_UNSUPPORTED_INT(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL) \
+-SSHCONF_UNSUPPORTED_INT(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL)
++SSHCONF_UNSUPPORTED_INT(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL) \
++SSHCONF_UNSUPPORTED_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL)
+ #endif /* GSSAPI */
+ 
+ #define SSHD_CONFIG_ENTRIES \
+diff --git a/ssh-gss.h b/ssh-gss.h
+index 329dc9da0..1506719a9 100644
+--- a/ssh-gss.h
++++ b/ssh-gss.h
+@@ -34,6 +34,12 @@
+ #include <gssapi/gssapi.h>
+ #endif
+ 
++#ifdef HAVE_GSSAPI_EXT_H
++#include <gssapi_ext.h>
++#elif defined(HAVE_GSSAPI_GSSAPI_EXT_H)
++#include <gssapi/gssapi_ext.h>
++#endif
++
+ #ifdef KRB5
+ # ifndef HEIMDAL
+ #  ifdef HAVE_GSSAPI_GENERIC_H
+@@ -112,6 +118,7 @@ typedef struct {
+ 	ssh_gssapi_ccache store;
+ 	int used;
+ 	int updated;
++	char **indicators; /* auth indicators */
+ } ssh_gssapi_client;
+ 
+ typedef struct ssh_gssapi_mech_struct {
+diff --git a/sshd_config.5 b/sshd_config.5
+index 5998275d2..8fed58d5c 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -811,6 +811,52 @@ This option only applies to connections using GSSAPI.
+ .Pp
+ The list of supported key exchange algorithms may also be obtained using
+ .Qq ssh -Q GSSAPIKexAlgorithms .
++.It Cm GSSAPIIndicators
++Specifies whether to accept or deny GSSAPI authenticated access if Kerberos
++mechanism is used and Kerberos ticket contains a particular set of
++authentication indicators. The values can be specified as a comma-separated list
++.Cm [!]name1,[!]name2,... .
++When indicator's name is prefixed with !, the authentication indicator 'name'
++will deny access to the system. Otherwise, one of non-negated authentication
++indicators must be present in the Kerberos ticket to allow access. If
++.Cm GSSAPIIndicators
++is defined, a Kerberos ticket that has indicators but does not match the
++policy will get denial. If at least one indicator is configured, whether for
++access or denial, tickets without authentication indicators will be explicitly
++rejected.
++.Pp
++By default systems using MIT Kerberos 1.17 or later will not assign any
++indicators. SPAKE and PKINIT methods add authentication indicators
++to all successful authentications. The SPAKE pre-authentication method is
++preferred over an encrypted timestamp pre-authentication when passwords used to
++authenticate user principals. Kerberos KDCs built with Heimdal Kerberos
++(including Samba AD DC built with Heimdal) do not add authentication
++indicators. However, OpenSSH built against Heimdal Kerberos library is able to
++inquire authentication indicators and thus can be used to check for their presence.
++.Pp
++Indicator name is case-sensitive and depends on the configuration of a
++particular Kerberos deployment. Indicators available in MIT Kerberos and
++FreeIPA environments:
++.Pp
++.Bl -tag -width XXXX -offset indent -compact
++.It Cm hardened
++SPAKE or encrypted timestamp pre-authentication mechanisms in MIT Kerberos and FreeIPA
++.It Cm pkinit
++smartcard or PKCS11 token-based pre-authentication in MIT Kerberos and FreeIPA
++.It Cm radius
++pre-authentication based on a RADIUS server in MIT Kerberos and FreeIPA
++.It Cm otp
++TOTP/HOTP-based two-factor pre-authentication in FreeIPA
++.It Cm idp
++OAuth2-based pre-authentication in FreeIPA using an external identity provider
++and device authorization grant flow
++.It Cm passkey
++FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens
++.El
++.Pp
++The default
++.Dq none
++is to not use GSSAPI authentication indicators for access decisions.
+ .It Cm HostbasedAcceptedAlgorithms
+ The default is handled system-wide by
+ .Xr crypto-policies 7 .
+-- 
+2.55.0
+

diff --git a/0047-NIST-curves-hybrid-KEX-implementation.patch b/0047-NIST-curves-hybrid-KEX-implementation.patch
new file mode 100644
index 0000000..0783c35
--- /dev/null
+++ b/0047-NIST-curves-hybrid-KEX-implementation.patch
@@ -0,0 +1,1307 @@
+From 8b2e741ada618336f01c58f61524f8d4b28cac3a Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 20 Oct 2025 16:07:31 +0200
+Subject: [PATCH 47/54] NIST curves hybrid KEX implementation
+
+---
+ compat.c                         |  23 +-
+ compat.h                         |   1 +
+ crypto_api.h                     |   5 +
+ kex-names.c                      |  89 +++-
+ kex.c                            |   1 +
+ kex.h                            |  19 +
+ kexgen.c                         |  56 ++-
+ kexmlkem768x25519.c              | 680 +++++++++++++++++++++++++++++--
+ monitor.c                        |   2 +
+ myproposal.h                     |   4 +
+ regress/unittests/kex/test_kex.c |   4 +
+ ssh-keyscan.c                    |   2 +
+ ssh_api.c                        |   4 +
+ sshconnect2.c                    |   2 +
+ sshd-auth.c                      |   2 +
+ 15 files changed, 853 insertions(+), 41 deletions(-)
+
+diff --git a/compat.c b/compat.c
+index 4312510e5..e6c7935d4 100644
+--- a/compat.c
++++ b/compat.c
+@@ -36,6 +36,7 @@
+ #include "compat.h"
+ #include "log.h"
+ #include "match.h"
++#include <openssl/fips.h>
+ 
+ /* determine bug flags from SSH protocol banner */
+ void
+@@ -148,8 +149,9 @@ char *
+ compat_kex_proposal(struct ssh *ssh, const char *p)
+ {
+ 	char *cp = NULL, *cp2 = NULL;
++	int mlkem_available = is_mlkem768_available();
+ 
+-	if ((ssh->compat & (SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX)) == 0)
++	if ((ssh->compat & (SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX)) == 0 && mlkem_available == 2)
+ 		return xstrdup(p);
+ 	debug2_f("original KEX proposal: %s", p);
+ 	if ((ssh->compat & SSH_BUG_CURVE25519PAD) != 0)
+@@ -164,6 +166,25 @@ compat_kex_proposal(struct ssh *ssh, const char *p)
+ 		free(cp);
+ 		cp = cp2;
+ 	}
++	if (mlkem_available == 2)
++		return cp ? cp : xstrdup(p);
++	if (mlkem_available == 1 && FIPS_mode()) {
++		if ((cp2 = match_filter_denylist(cp ? cp : p,
++		    "mlkem768x25519-sha256")) == NULL)
++			fatal("match_filter_denylist failed");
++		free(cp);
++		cp = cp2;
++	}
++	if (mlkem_available == 0) {
++		if ((cp2 = match_filter_denylist(cp ? cp : p,
++		    "mlkem768x25519-sha256,"
++		    "mlkem768nistp256-sha256,"
++		    "mlkem1024nistp384-sha384")) == NULL)
++			fatal("match_filter_denylist failed");
++		free(cp);
++		cp = cp2;
++	}
++
+ 	if (cp == NULL || *cp == '\0')
+ 		fatal("No supported key exchange algorithms found");
+ 	debug2_f("compat KEX proposal: %s", cp);
+diff --git a/compat.h b/compat.h
+index 2e6db5bf9..b78e55b69 100644
+--- a/compat.h
++++ b/compat.h
+@@ -62,4 +62,5 @@ struct ssh;
+ 
+ void    compat_banner(struct ssh *, const char *);
+ char	*compat_kex_proposal(struct ssh *, const char *);
++int is_mlkem768_available(void);
+ #endif
+diff --git a/crypto_api.h b/crypto_api.h
+index 9095d8292..f91556b2d 100644
+--- a/crypto_api.h
++++ b/crypto_api.h
+@@ -203,6 +203,11 @@ int crypto_sign_mldsa44_ed25519_verify(const uint8_t sig[MLDSA44_ED25519_SIG_SZ]
+     const uint8_t *ctx, size_t ctxlen,
+     const uint8_t pk[MLDSA44_ED25519_PK_SZ]);
+ 
++/* ML-KEM-1024 */
++#define crypto_kem_mlkem1024_PUBLICKEYBYTES 1568
++#define crypto_kem_mlkem1024_SECRETKEYBYTES 3168
++#define crypto_kem_mlkem1024_CIPHERTEXTBYTES 1568
++
+ /* Utility */
+ void sha3_256(uint8_t digest[32], const uint8_t *data, size_t len);
+ void sha3_512(uint8_t digest[64], const uint8_t *data, size_t len);
+diff --git a/kex-names.c b/kex-names.c
+index 6cc8ab47c..aa4a5eaab 100644
+--- a/kex-names.c
++++ b/kex-names.c
+@@ -34,6 +34,7 @@
+ #include <openssl/crypto.h>
+ #include <openssl/fips.h>
+ #include <openssl/evp.h>
++#include <openssl/err.h>
+ #endif
+ 
+ #include "kex.h"
+@@ -90,6 +91,10 @@ static const struct kexalg kexalgs[] = {
+ #ifdef USE_MLKEM768X25519
+ 	{ KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0,
+ 	    SSH_DIGEST_SHA256, KEX_IS_PQ },
++	{ KEX_MLKEM768NISTP256_SHA256, KEX_KEM_MLKEM768NISTP256_SHA256, 0,
++	    SSH_DIGEST_SHA256, KEX_IS_PQ },
++	{ KEX_MLKEM1024NISTP384_SHA384, KEX_KEM_MLKEM1024NISTP384_SHA384, 0,
++	    SSH_DIGEST_SHA384, KEX_IS_PQ },
+ #endif
+ #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */
+ 	{ NULL, 0, -1, -1, 0 },
+@@ -108,14 +113,27 @@ static const struct kexalg gss_kexalgs[] = {
+ 	{ NULL, 0, -1, -1, 0},
+ };
+ 
+-static int is_mlkem768_available()
++/*
++ * 0 - unavailable
++ * 1 - available in non-FIPS mode
++ * 2 - available always
++ */
++int is_mlkem768_available()
+ {
+ 	static int is_fetched = -1;
+ 
+ 	if (is_fetched == -1) {
+-		EVP_KEM *mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", NULL);
+-		is_fetched = mlkem768 != NULL ? 1 : 0;
++		EVP_KEM *mlkem768 = NULL;
++
++		ERR_set_mark();
++		mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", NULL);
++		is_fetched = (mlkem768 == NULL) ? 0 : 2;
++		if (is_fetched == 0 && FIPS_mode() == 1) {
++		    mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", "provider=default,-fips");
++		    is_fetched = (mlkem768 == NULL) ? 0 : 1;
++		}
+ 		EVP_KEM_free(mlkem768);
++		ERR_pop_to_mark();
+ 	}
+ 
+ 	return is_fetched;
+@@ -127,11 +145,32 @@ kex_alg_list_internal(char sep, const struct kexalg *algs)
+ 	char *ret = NULL;
+ 	const struct kexalg *k;
+ 	char sep_str[2] = {sep, '\0'};
++	int x25519mlkem_available = 0, nistmlkem_available = 0;
+ 
+-	for (k = kexalgs; k->name != NULL; k++) {
+-		if (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0
+-			&& !is_mlkem768_available())
++	/*
++	 * FIPS provider can provide ML-KEMs and then all hybrids are available
++	 * Otherwise only NIST hybrids are available
++	 * */
++	if (FIPS_mode()) {
++	    if (is_mlkem768_available() == 2) {
++	        x25519mlkem_available = 1;
++	        nistmlkem_available = 1;
++	    } else if (is_mlkem768_available() == 1) {
++	        nistmlkem_available = 1;
++	    }
++	} else {
++	    if (is_mlkem768_available() > 0) {
++	        x25519mlkem_available = 1;
++	        nistmlkem_available = 1;
++	    }
++	}
++
++	for (k = algs; k->name != NULL; k++) {
++		if (  (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0    && x25519mlkem_available == 0)
++		   || (strcmp(k->name, KEX_MLKEM768NISTP256_SHA256) == 0  && nistmlkem_available == 0)
++		   || (strcmp(k->name, KEX_MLKEM1024NISTP384_SHA384) == 0 && nistmlkem_available == 0))
+ 			continue;
++
+ 		xextendf(&ret, sep_str, "%s", k->name);
+ 	}
+ 
+@@ -154,10 +193,30 @@ static const struct kexalg *
+ kex_alg_by_name(const char *name)
+ {
+ 	const struct kexalg *k;
++	int x25519mlkem_available = 0, nistmlkem_available = 0;
+ 
+-	if (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0
+-		&& !is_mlkem768_available())
+-	return NULL;
++	/*
++	 * FIPS provider can provide ML-KEMs and then all hybrids are available
++	 * Otherwise only NIST hybrids are available
++	 * */
++	if (FIPS_mode()) {
++	    if (is_mlkem768_available() == 2) {
++	        x25519mlkem_available = 1;
++	        nistmlkem_available = 1;
++	    } else if (is_mlkem768_available() == 1) {
++	        nistmlkem_available = 1;
++	    }
++	} else {
++	    if (is_mlkem768_available() > 0) {
++	        x25519mlkem_available = 1;
++	        nistmlkem_available = 1;
++	    }
++	}
++
++	if (  (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0    && x25519mlkem_available == 0)
++	   || (strcmp(name, KEX_MLKEM768NISTP256_SHA256) == 0  && nistmlkem_available == 0)
++	   || (strcmp(name, KEX_MLKEM1024NISTP384_SHA384) == 0 && nistmlkem_available == 0))
++	   return NULL;
+ 
+ 	for (k = kexalgs; k->name != NULL; k++) {
+ 		if (strcmp(k->name, name) == 0)
+@@ -235,8 +294,16 @@ kex_names_valid(const char *names)
+ 			return 0;
+ 		}
+ 		if (kex_alg_by_name(p) == NULL) {
+-			if (FIPS_mode())
+-				error("\"%.100s\" is not allowed in FIPS mode", p);
++			if (FIPS_mode()) {
++				if ((strcmp(p, KEX_MLKEM768X25519_SHA256) == 0)
++				    || (strcmp(p, KEX_MLKEM768NISTP256_SHA256) == 0)
++				    || (strcmp(p, KEX_MLKEM1024NISTP384_SHA384) == 0)) {
++					debug("\"%.100s\" is not allowed in FIPS mode", p);
++					continue;
++				}
++				else
++					error("\"%.100s\" is not allowed in FIPS mode", p);
++			}
+ 			else
+ 				error("Unsupported KEX algorithm \"%.100s\"", p);
+ 			free(s);
+diff --git a/kex.c b/kex.c
+index a51e26398..136ba318e 100644
+--- a/kex.c
++++ b/kex.c
+@@ -762,6 +762,7 @@ kex_free(struct kex *kex)
+ #ifdef OPENSSL_HAS_ECC
+ 	EC_KEY_free(kex->ec_client_key);
+ #endif /* OPENSSL_HAS_ECC */
++	EVP_PKEY_free(kex->ec_hybrid_client_key);
+ #endif /* WITH_OPENSSL */
+ 	for (mode = 0; mode < MODE_MAX; mode++) {
+ 		kex_free_newkeys(kex->newkeys[mode]);
+diff --git a/kex.h b/kex.h
+index d22f6deb3..9b5fe05c4 100644
+--- a/kex.h
++++ b/kex.h
+@@ -72,6 +72,8 @@
+ #define	KEX_SNTRUP761X25519_SHA512	"sntrup761x25519-sha512"
+ #define	KEX_SNTRUP761X25519_SHA512_OLD	"sntrup761x25519-sha512@openssh.com"
+ #define	KEX_MLKEM768X25519_SHA256	"mlkem768x25519-sha256"
++#define	KEX_MLKEM768NISTP256_SHA256     "mlkem768nistp256-sha256"
++#define	KEX_MLKEM1024NISTP384_SHA384    "mlkem1024nistp384-sha384"
+ 
+ #define COMP_NONE	0
+ #define COMP_DELAYED	2
+@@ -110,6 +112,8 @@ enum kex_exchange {
+ 	KEX_C25519_SHA256,
+ 	KEX_KEM_SNTRUP761X25519_SHA512,
+ 	KEX_KEM_MLKEM768X25519_SHA256,
++	KEX_KEM_MLKEM768NISTP256_SHA256,
++	KEX_KEM_MLKEM1024NISTP384_SHA384,
+ #ifdef GSSAPI
+ 	KEX_GSS_GRP1_SHA1,
+ 	KEX_GSS_GRP14_SHA1,
+@@ -212,6 +216,9 @@ struct kex {
+ 	u_char sntrup761_client_key[crypto_kem_sntrup761_SECRETKEYBYTES]; /* KEM */
+ 	u_char mlkem768_client_key[crypto_kem_mlkem768_SECRETKEYBYTES]; /* KEM */
+ 	struct sshbuf *client_pub;
++	/* FIXME */
++	EVP_PKEY *ec_hybrid_client_key; /* NIST hybrids */
++	u_char mlkem1024_client_key[crypto_kem_mlkem1024_SECRETKEYBYTES]; /* ML-KEM 1024 + NIST */
+ };
+ 
+ int	 kex_name_valid(const char *);
+@@ -294,6 +301,18 @@ int	 kex_kem_mlkem768x25519_enc(struct kex *, const struct sshbuf *,
+ int	 kex_kem_mlkem768x25519_dec(struct kex *, const struct sshbuf *,
+     struct sshbuf **);
+ 
++int	 kex_kem_mlkem768nistp256_keypair(struct kex *);
++int	 kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *,
++    struct sshbuf **, struct sshbuf **);
++int	 kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *,
++    struct sshbuf **);
++
++int	 kex_kem_mlkem1024nistp384_keypair(struct kex *);
++int	 kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *,
++    struct sshbuf **, struct sshbuf **);
++int	 kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *,
++    struct sshbuf **);
++
+ int	 kex_dh_keygen(struct kex *);
+ int	 kex_dh_compute_key(struct kex *, BIGNUM *, struct sshbuf *);
+ 
+diff --git a/kexgen.c b/kexgen.c
+index 2f8252488..9a356e298 100644
+--- a/kexgen.c
++++ b/kexgen.c
+@@ -133,12 +133,24 @@ kex_gen_client(struct ssh *ssh)
+ 		break;
+ 	case KEX_KEM_MLKEM768X25519_SHA256:
+ 		if (FIPS_mode()) {
+-		    logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
+-		    r = SSH_ERR_INVALID_ARGUMENT;
++		    EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
++		    if (mlkem == NULL) {
++		        logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
++		        r = SSH_ERR_INVALID_ARGUMENT;
++		    } else {
++			EVP_KEM_free(mlkem);
++		        r = kex_kem_mlkem768x25519_keypair(kex);
++		    }
+ 		} else {
+ 		    r = kex_kem_mlkem768x25519_keypair(kex);
+ 		}
+ 		break;
++	case KEX_KEM_MLKEM768NISTP256_SHA256:
++		    r = kex_kem_mlkem768nistp256_keypair(kex);
++		break;
++	case KEX_KEM_MLKEM1024NISTP384_SHA384:
++		    r = kex_kem_mlkem1024nistp384_keypair(kex);
++		break;
+ 	default:
+ 		r = SSH_ERR_INVALID_ARGUMENT;
+ 		break;
+@@ -223,13 +235,28 @@ input_kex_gen_reply(int type, uint32_t seq, struct ssh *ssh)
+ 		break;
+ 	case KEX_KEM_MLKEM768X25519_SHA256:
+ 		if (FIPS_mode()) {
+-		    logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
+-		    r = SSH_ERR_INVALID_ARGUMENT;
++		    EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
++		    if (mlkem == NULL) {
++		        logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
++		        r = SSH_ERR_INVALID_ARGUMENT;
++		    } else {
++			EVP_KEM_free(mlkem);
++		        r = kex_kem_mlkem768x25519_dec(kex, server_blob,
++		            &shared_secret);
++		    }
+ 		} else {
+ 		    r = kex_kem_mlkem768x25519_dec(kex, server_blob,
+ 		        &shared_secret);
+ 		}
+ 		break;
++	case KEX_KEM_MLKEM768NISTP256_SHA256:
++		    r = kex_kem_mlkem768nistp256_dec(kex, server_blob,
++		        &shared_secret);
++		break;
++	case KEX_KEM_MLKEM1024NISTP384_SHA384:
++		    r = kex_kem_mlkem1024nistp384_dec(kex, server_blob,
++		        &shared_secret);
++		break;
+ 	default:
+ 		r = SSH_ERR_INVALID_ARGUMENT;
+ 		break;
+@@ -283,6 +310,8 @@ out:
+ 	    sizeof(kex->sntrup761_client_key));
+ 	explicit_bzero(kex->mlkem768_client_key,
+ 	    sizeof(kex->mlkem768_client_key));
++	explicit_bzero(kex->mlkem1024_client_key,
++	    sizeof(kex->mlkem1024_client_key));
+ 	sshbuf_free(server_host_key_blob);
+ 	free(signature);
+ 	sshbuf_free(tmp);
+@@ -362,13 +391,28 @@ input_kex_gen_init(int type, uint32_t seq, struct ssh *ssh)
+ 		break;
+ 	case KEX_KEM_MLKEM768X25519_SHA256:
+ 		if (FIPS_mode()) {
+-		    logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
+-		    r = SSH_ERR_INVALID_ARGUMENT;
++		    EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
++		    if (mlkem == NULL) {
++		        logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
++		        r = SSH_ERR_INVALID_ARGUMENT;
++		    } else {
++			EVP_KEM_free(mlkem);
++		        r = kex_kem_mlkem768x25519_enc(kex, client_pubkey,
++		            &server_pubkey, &shared_secret);
++		    }
+ 		} else {
+ 		    r = kex_kem_mlkem768x25519_enc(kex, client_pubkey,
+ 		        &server_pubkey, &shared_secret);
+ 		}
+ 		break;
++	case KEX_KEM_MLKEM768NISTP256_SHA256:
++		    r = kex_kem_mlkem768nistp256_enc(kex, client_pubkey,
++		        &server_pubkey, &shared_secret);
++		break;
++	case KEX_KEM_MLKEM1024NISTP384_SHA384:
++		    r = kex_kem_mlkem1024nistp384_enc(kex, client_pubkey,
++		        &server_pubkey, &shared_secret);
++		break;
+ 	default:
+ 		r = SSH_ERR_INVALID_ARGUMENT;
+ 		break;
+diff --git a/kexmlkem768x25519.c b/kexmlkem768x25519.c
+index 36658120f..03efb788f 100644
+--- a/kexmlkem768x25519.c
++++ b/kexmlkem768x25519.c
+@@ -45,19 +45,34 @@
+ 
+ #ifdef USE_MLKEM768X25519
+ 
++#include "libcrux_internal.h"
++#include <openssl/bn.h>
++#include <openssl/ec.h>
+ #include <openssl/err.h>
+ #include <openssl/evp.h>
++#include <openssl/fips.h>
+ #include <stdio.h>
+ 
++#define FIPS_FALLBACK_PROPQ "provider=default,-fips"
++
+ static int
+-mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
++mlkem_keypair_gen(const char *algname, unsigned char *pubkeybuf, size_t pubkey_size,
++	          unsigned char *privkeybuf, size_t privkey_size)
+ {
+     EVP_PKEY_CTX *ctx = NULL;
+     EVP_PKEY *pkey = NULL;
+     int ret = SSH_ERR_INTERNAL_ERROR;
+-    size_t pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES, privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES;
++    size_t got_pub_size = pubkey_size, got_priv_size = privkey_size;
++
++    ctx = EVP_PKEY_CTX_new_from_name(NULL, algname, NULL);
++
++    if (ctx == NULL && FIPS_mode()) {
++	/* We have filtered x25519 + ML-KEM in FIPS mode earlier
++	 * so if we are in FIPS mode and ML-KEM is not available with default propq,
++	 * we can fetch it from the default provider */
++        ctx = EVP_PKEY_CTX_new_from_name(NULL, algname, FIPS_FALLBACK_PROPQ);
++    }
+ 
+-    ctx = EVP_PKEY_CTX_new_from_name(NULL, "mlkem768", NULL);
+     if (ctx == NULL) {
+ 	ret = SSH_ERR_LIBCRYPTO_ERROR;
+ 	goto err;
+@@ -69,17 +84,23 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
+         goto err;
+     }
+ 
+-    if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &pubkey_size) <= 0
+-	|| EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &privkey_size) <= 0) {
++    if (EVP_PKEY_get_raw_public_key(pkey, NULL, &got_pub_size) <= 0
++	|| EVP_PKEY_get_raw_private_key(pkey, NULL, &got_priv_size) <= 0) {
+ 	ret = SSH_ERR_LIBCRYPTO_ERROR;
+         goto err;
+     }
+ 
+-    if (privkey_size != crypto_kem_mlkem768_SECRETKEYBYTES
+-	    || pubkey_size != crypto_kem_mlkem768_PUBLICKEYBYTES) {
++    if (privkey_size != got_priv_size || pubkey_size != got_pub_size) {
+ 	ret = SSH_ERR_LIBCRYPTO_ERROR;
+         goto err;
+     }
++
++    if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &got_pub_size) <= 0
++	|| EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &got_priv_size) <= 0) {
++	ret = SSH_ERR_LIBCRYPTO_ERROR;
++        goto err;
++    }
++
+     ret = 0;
+ 
+  err:
+@@ -91,27 +112,57 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
+ }
+ 
+ static int
+-mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
++mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
++{
++	return mlkem_keypair_gen("mlkem768", pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES,
++			privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES);
++}
++
++static int
++mlkem1024_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
++{
++	return mlkem_keypair_gen("mlkem1024", pubkeybuf, crypto_kem_mlkem1024_PUBLICKEYBYTES,
++			privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES);
++}
++
++static int
++mlkem_encap_secret(const char *mlkem_alg, const u_char *pubkeybuf, u_char *secret, u_char *out)
+ {
+     EVP_PKEY *pkey = NULL;
+     EVP_PKEY_CTX *ctx = NULL;
+     int r = SSH_ERR_INTERNAL_ERROR;
+-    size_t outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES,
+-	   secretlen = crypto_kem_mlkem768_BYTES;
++    size_t outlen, expected_outlen, publen, secretlen = crypto_kem_mlkem768_BYTES;
++    int fips_fallback = 0;
++
++    if (strcmp(mlkem_alg, "mlkem768") == 0) {
++	    outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES;
++	    publen = crypto_kem_mlkem768_PUBLICKEYBYTES;
++    } else if (strcmp(mlkem_alg, "mlkem1024") == 0) {
++	    outlen = crypto_kem_mlkem1024_CIPHERTEXTBYTES;
++	    publen = crypto_kem_mlkem1024_PUBLICKEYBYTES;
++    } else
++	    return r;
+ 
+-    pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "mlkem768", NULL,
+-		    pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES);
++    expected_outlen = outlen;
++
++    pkey = EVP_PKEY_new_raw_public_key_ex(NULL, mlkem_alg, NULL,
++		    pubkeybuf, publen);
++    if (pkey == NULL && FIPS_mode()) {
++        pkey = EVP_PKEY_new_raw_public_key_ex(NULL, mlkem_alg, FIPS_FALLBACK_PROPQ,
++		    pubkeybuf, publen);
++	fips_fallback = 1;
++    }
+     if (pkey == NULL) {
+ 	r = SSH_ERR_LIBCRYPTO_ERROR;
+ 	goto err;
+     }
+ 
+-    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
++    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, fips_fallback ? FIPS_FALLBACK_PROPQ : NULL);
+     if (ctx == NULL
+ 	|| EVP_PKEY_encapsulate_init(ctx, NULL) <= 0
+-        || EVP_PKEY_encapsulate(ctx, out, &outlen, secret, &secretlen) <= 0
++        || EVP_PKEY_encapsulate(ctx, out, &expected_outlen, secret, &secretlen) <= 0
+ 	|| secretlen != crypto_kem_mlkem768_BYTES
+-	|| outlen != crypto_kem_mlkem768_CIPHERTEXTBYTES) {
++	|| outlen != expected_outlen) {
+ 	r = SSH_ERR_LIBCRYPTO_ERROR;
+ 	goto err;
+     }
+@@ -127,25 +178,45 @@ mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
+ }
+ 
+ static int
+-mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
++mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
++{
++	return mlkem_encap_secret("mlkem768", pubkeybuf, secret, out);
++}
++
++static int
++mlkem1024_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
++{
++	return mlkem_encap_secret("mlkem1024", pubkeybuf, secret, out);
++}
++
++static int
++mlkem_decap_secret(const char *algname,
++    const u_char *privkeybuf, size_t privkey_len,
++    const u_char *wrapped, size_t wrapped_len,
++    u_char *secret)
+ {
+     EVP_PKEY *pkey = NULL;
+     EVP_PKEY_CTX *ctx = NULL;
+     int r = SSH_ERR_INTERNAL_ERROR;
+-    size_t wrappedlen = crypto_kem_mlkem768_CIPHERTEXTBYTES,
+-	   secretlen = crypto_kem_mlkem768_BYTES;
++    size_t secretlen = crypto_kem_mlkem768_BYTES;
++    int fips_fallback = 0;
+ 
+-    pkey = EVP_PKEY_new_raw_private_key_ex(NULL, "mlkem768", NULL,
+-		    privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES);
++    pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algname,
++		    NULL, privkeybuf, privkey_len);
++    if (pkey == NULL && FIPS_mode()) {
++        pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algname,
++		    FIPS_FALLBACK_PROPQ, privkeybuf, privkey_len);
++	fips_fallback = 1;
++    }
+     if (pkey == NULL) {
+ 	r = SSH_ERR_LIBCRYPTO_ERROR;
+ 	goto err;
+     }
+ 
+-    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
++    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, fips_fallback ? FIPS_FALLBACK_PROPQ : NULL);
+     if (ctx == NULL
+ 	|| EVP_PKEY_decapsulate_init(ctx, NULL) <= 0
+-        || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrappedlen) <= 0
++        || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrapped_len) <= 0
+ 	|| secretlen != crypto_kem_mlkem768_BYTES) {
+ 	r = SSH_ERR_LIBCRYPTO_ERROR;
+ 	goto err;
+@@ -162,6 +233,20 @@ mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *s
+     return r;
+ }
+ 
++static int
++mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
++{
++	return mlkem_decap_secret("mlkem768", privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES,
++		wrapped, crypto_kem_mlkem768_CIPHERTEXTBYTES, secret);
++}
++
++static int
++mlkem1024_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
++{
++	return mlkem_decap_secret("mlkem1024", privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES,
++		wrapped, crypto_kem_mlkem1024_CIPHERTEXTBYTES, secret);
++}
++
+ int
+ kex_kem_mlkem768x25519_keypair(struct kex *kex)
+ {
+@@ -327,7 +412,7 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
+ 	u_char hash[SSH_DIGEST_MAX_LENGTH];
+ 	size_t need;
+ 	int r = SSH_ERR_INTERNAL_ERROR;
+-	struct libcrux_mlkem768_enc_result enc; /* FIXME */
++	libcrux_mlkem768_enc_result enc;
+ 
+ 	*server_blobp = NULL;
+ 	*shared_secretp = NULL;
+@@ -529,6 +614,520 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
+ 	return r;
+ #endif
+ }
++
++#define NIST_P256_COMPRESSED_LEN   33
++#define NIST_P256_UNCOMPRESSED_LEN 65
++#define NIST_P384_COMPRESSED_LEN   49
++#define NIST_P384_UNCOMPRESSED_LEN 97
++#define NIST_BUF_MAX_SIZE NIST_P384_UNCOMPRESSED_LEN
++
++static const char ec256[] = "P-256";
++static const char ec384[] = "P-384";
++static const char *len2curve_name(size_t len)
++{
++	switch (len) {
++		case NIST_P256_COMPRESSED_LEN:
++		case NIST_P256_UNCOMPRESSED_LEN:
++			return ec256;
++			break;
++		case NIST_P384_COMPRESSED_LEN:
++		case NIST_P384_UNCOMPRESSED_LEN:
++			return ec384;
++			break;
++	}
++	return NULL;
++}
++
++static EVP_PKEY *
++buf2nist_key(const unsigned char *pub_key_buf, size_t pub_key_len)
++{
++	EVP_PKEY *pkey = NULL;
++	EVP_PKEY_CTX *ctx = NULL;
++	OSSL_PARAM params[3];
++	const char *curve_name = len2curve_name(pub_key_len);
++
++	if (curve_name == NULL)
++		return NULL;
++
++	ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
++	if (!ctx)
++		goto err;
++
++	if (EVP_PKEY_fromdata_init(ctx) <= 0)
++		goto err;
++
++	params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
++	params[1] = OSSL_PARAM_construct_octet_string(
++			OSSL_PKEY_PARAM_PUB_KEY, (void *)pub_key_buf, pub_key_len);
++	params[2] = OSSL_PARAM_construct_end();
++
++	if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
++		goto err;
++
++	EVP_PKEY_CTX_free(ctx);
++	return pkey;
++
++err:
++	EVP_PKEY_CTX_free(ctx);
++	EVP_PKEY_free(pkey);
++	return NULL;
++}
++
++static int
++kex_nist_shared_key_ext(EVP_PKEY *priv_key,
++    const u_char *pub_key_buf, size_t pub_key_len, struct sshbuf *out)
++{
++	EVP_PKEY_CTX *ctx = NULL;
++	unsigned char *shared_secret = NULL;
++	size_t shared_secret_len = 0;
++	EVP_PKEY *peer_key = buf2nist_key(pub_key_buf, pub_key_len);
++	int r = SSH_ERR_INTERNAL_ERROR;
++
++	if (peer_key == NULL)
++		return SSH_ERR_KEY_LENGTH;
++
++	ctx = EVP_PKEY_CTX_new_from_pkey(NULL, priv_key, NULL);
++	if (!ctx)
++		goto end;
++
++	if ((EVP_PKEY_derive_init(ctx) <= 0)
++		|| EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0
++		|| EVP_PKEY_derive(ctx, NULL, &shared_secret_len) <= 0)
++		goto end;
++
++	shared_secret = OPENSSL_malloc(shared_secret_len);
++	if (shared_secret == NULL)
++		goto end;
++
++	if (EVP_PKEY_derive(ctx, shared_secret, &shared_secret_len) <= 0)
++		goto end;
++
++	if ((r = sshbuf_put(out, shared_secret, shared_secret_len)) != 0)
++		goto end;
++
++	r = 0;
++
++end:
++	EVP_PKEY_free(peer_key);
++	if (shared_secret)
++		OPENSSL_clear_free(shared_secret, shared_secret_len);
++	EVP_PKEY_CTX_free(ctx);
++
++	return r;
++}
++
++static EVP_PKEY *
++nist_pkey_keygen(size_t pub_key_len)
++{
++	const char *curve_name = len2curve_name(pub_key_len);
++	EVP_PKEY_CTX *pctx = NULL;
++	EVP_PKEY *pkey = NULL;
++	OSSL_PARAM params[2];
++
++	if (curve_name == NULL)
++		return NULL;
++
++	pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
++	if (!pctx)
++		return NULL;
++
++	if (EVP_PKEY_keygen_init(pctx) <= 0) {
++		EVP_PKEY_CTX_free(pctx);
++		return NULL;
++	}
++
++	params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
++	params[1] = OSSL_PARAM_construct_end();
++
++	if (EVP_PKEY_CTX_set_params(pctx, params) <= 0
++	    || EVP_PKEY_keygen(pctx, &pkey) <= 0) {
++		EVP_PKEY_CTX_free(pctx);
++		return NULL;
++	}
++
++	EVP_PKEY_CTX_free(pctx);
++	return pkey;
++}
++
++static size_t decompress_pub_key(void *pub, size_t compressed_len, size_t decompressed_len)
++{
++    EC_GROUP *group = NULL;
++    EC_POINT *point = NULL;
++    BN_CTX *ctx = NULL;
++    size_t len = 0;
++    int group_nid = NID_undef;
++
++    switch (compressed_len) {
++    case NIST_P256_COMPRESSED_LEN:
++         group_nid = NID_X9_62_prime256v1;
++       break;
++    case NIST_P384_COMPRESSED_LEN:
++         group_nid = NID_secp384r1;
++       break;
++    default:
++       return 0;
++       break;
++    }
++
++    ctx = BN_CTX_new();
++    group = EC_GROUP_new_by_curve_name(group_nid);
++    if (ctx == NULL || group == NULL)
++        goto err;
++
++    point = EC_POINT_new(group);
++    if (point == NULL)
++        goto err;
++
++    if (!EC_POINT_oct2point(group, point, pub, compressed_len, ctx))
++        goto err;
++
++    len = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, pub, decompressed_len, ctx);
++
++err:
++    EC_POINT_free(point);
++    EC_GROUP_free(group);
++    BN_CTX_free(ctx);
++
++    return len;
++}
++
++static int
++get_uncompressed_ec_pubkey(EVP_PKEY *pkey, unsigned char *buf, size_t buf_len)
++{
++    OSSL_PARAM params[2];
++    size_t required_len = 0, out_len = 0;
++
++    params[0] = OSSL_PARAM_construct_utf8_string(
++        OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT,
++        "uncompressed", 0);
++    params[1] = OSSL_PARAM_construct_end();
++
++    if (EVP_PKEY_set_params(pkey, params) <= 0
++	    || EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
++                                          buf, buf_len, &required_len) <= 0) {
++        return SSH_ERR_LIBCRYPTO_ERROR;
++    }
++
++    if (required_len != buf_len) {
++        /* Red Hat certified FIPS provider ignores OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT
++	 * We may have to perform the conversion manually */
++        if (len2curve_name(required_len) == len2curve_name(buf_len)) {
++	    out_len = decompress_pub_key(buf, required_len, buf_len);
++	    if (out_len != buf_len) {
++	        debug_f("Error decompressing the compressed public key");
++	        return SSH_ERR_LIBCRYPTO_ERROR;
++	    } else {
++		return 0;
++	    }
++	} else {
++	    debug_f("Unexpected length of uncompressed public key: expected %d, got %d", buf_len, required_len);
++	    return SSH_ERR_LIBCRYPTO_ERROR;
++	}
++    }
++
++    return 0;
++}
++/* nist_bytes_len should always be uncompressed */
++static int
++kex_kem_mlkem_nist_keypair(struct kex *kex, size_t mlkem_bytes_len, size_t nist_bytes_len)
++{
++	struct sshbuf *buf = NULL;
++	u_char *cp = NULL;
++	size_t need;
++	int r = SSH_ERR_INTERNAL_ERROR;
++	u_char *client_key = NULL;
++
++	if ((buf = sshbuf_new()) == NULL)
++		return SSH_ERR_ALLOC_FAIL;
++	need = mlkem_bytes_len + nist_bytes_len;
++	if ((r = sshbuf_reserve(buf, need, &cp)) != 0)
++		goto out;
++
++	if (mlkem_bytes_len == crypto_kem_mlkem768_PUBLICKEYBYTES) {
++		client_key = kex->mlkem768_client_key;
++		r = mlkem768_keypair_gen(cp, client_key);
++	}
++
++	if (mlkem_bytes_len == crypto_kem_mlkem1024_PUBLICKEYBYTES) {
++		client_key = kex->mlkem1024_client_key;
++		r = mlkem1024_keypair_gen(cp, client_key);
++	}
++
++	if (client_key == NULL)
++		goto out;
++
++	if (r != 0)
++		goto out;
++#ifdef DEBUG_KEXECDH
++	dump_digest("client public key mlkemXXX:", cp, mlkem_bytes_len);
++#endif
++	cp += mlkem_bytes_len;
++	if ((kex->ec_hybrid_client_key = nist_pkey_keygen(nist_bytes_len)) == NULL)
++		goto out;
++
++	if ((r = get_uncompressed_ec_pubkey(kex->ec_hybrid_client_key, cp, nist_bytes_len)) != 0)
++		goto out;
++
++#ifdef DEBUG_KEXECDH
++	dump_digest("client public key NIST:", cp, nist_bytes_len);
++#endif
++	/* success */
++	r = 0;
++	kex->client_pub = buf;
++	buf = NULL;
++ out:
++	sshbuf_free(buf);
++        if (r == SSH_ERR_LIBCRYPTO_ERROR)
++	   ERR_print_errors_fp(stderr);
++
++	return r;
++}
++
++static int
++kex_kem_mlkem_nist_enc(struct kex *kex, const char *nist_curve,
++   const struct sshbuf *client_blob, struct sshbuf **server_blobp,
++   struct sshbuf **shared_secretp)
++{
++	struct sshbuf *server_blob = NULL;
++	struct sshbuf *buf = NULL;
++	const u_char *client_pub;
++	u_char server_pub[NIST_BUF_MAX_SIZE];
++	u_char enc_out[crypto_kem_mlkem1024_CIPHERTEXTBYTES];
++	u_char secret[crypto_kem_mlkem768_BYTES];
++	EVP_PKEY *server_key = NULL;
++	u_char hash[SSH_DIGEST_MAX_LENGTH];
++	size_t client_buf_len, mlkem_buf_len, ecdh_buf_len, server_key_len, enc_out_len;
++	int r = SSH_ERR_INTERNAL_ERROR;
++
++	*server_blobp = NULL;
++	*shared_secretp = NULL;
++
++	client_buf_len = sshbuf_len(client_blob);
++	/* client_blob contains both KEM and ECDH client pubkeys */
++	if (strcmp(nist_curve, "P-256") == 0) {
++		if (crypto_kem_mlkem768_PUBLICKEYBYTES > client_buf_len)
++			return r;
++
++		ecdh_buf_len = client_buf_len - crypto_kem_mlkem768_PUBLICKEYBYTES;
++		if (ecdh_buf_len != NIST_P256_COMPRESSED_LEN &&
++			ecdh_buf_len != NIST_P256_UNCOMPRESSED_LEN)
++			return r;
++		mlkem_buf_len = crypto_kem_mlkem768_PUBLICKEYBYTES;
++		enc_out_len = crypto_kem_mlkem768_CIPHERTEXTBYTES;
++		server_key_len = NIST_P256_UNCOMPRESSED_LEN;
++	} else if (strcmp(nist_curve, "P-384") == 0) {
++		if (crypto_kem_mlkem1024_PUBLICKEYBYTES > client_buf_len)
++			return r;
++
++		ecdh_buf_len = client_buf_len - crypto_kem_mlkem1024_PUBLICKEYBYTES;
++		if (ecdh_buf_len != NIST_P384_COMPRESSED_LEN &&
++			ecdh_buf_len != NIST_P384_UNCOMPRESSED_LEN)
++			return r;
++		mlkem_buf_len = crypto_kem_mlkem1024_PUBLICKEYBYTES;
++		enc_out_len = crypto_kem_mlkem1024_CIPHERTEXTBYTES;
++		server_key_len = NIST_P384_UNCOMPRESSED_LEN;
++	} else
++		return r;
++
++	client_pub = sshbuf_ptr(client_blob);
++#ifdef DEBUG_KEXECDH
++	dump_digest("client public key mlkem:", client_pub, mlkem_buf_len);
++	dump_digest("client public key NIST:", client_pub + mlkem_buf_len, ecdh_buf_len);
++#endif
++
++	/* allocate buffer for concatenation of KEM key and ECDH shared key */
++	/* the buffer will be hashed and the result is the shared secret */
++	if ((buf = sshbuf_new()) == NULL) {
++		r = SSH_ERR_ALLOC_FAIL;
++		goto out;
++	}
++	/* allocate space for encrypted KEM key and ECDH pub key */
++	if ((server_blob = sshbuf_new()) == NULL) {
++		r = SSH_ERR_ALLOC_FAIL;
++		goto out;
++	}
++	r = (mlkem_buf_len == crypto_kem_mlkem768_PUBLICKEYBYTES) ?
++		mlkem768_encap_secret(client_pub, secret, enc_out) :
++		mlkem1024_encap_secret(client_pub, secret, enc_out);
++
++	if (r != 0)
++		goto out;
++
++	/* generate ECDH key pair, store server pubkey after ciphertext */
++	server_key = nist_pkey_keygen(server_key_len);
++
++	if ((server_key == NULL) ||
++	    (r = get_uncompressed_ec_pubkey(server_key, server_pub, server_key_len) != 0) ||
++	    (r = sshbuf_put(buf, secret, sizeof(secret))) != 0 ||
++	    (r = sshbuf_put(server_blob, enc_out, enc_out_len) != 0)||
++	    (r = sshbuf_put(server_blob, server_pub, server_key_len)) != 0)
++		goto out;
++
++	/* append ECDH shared key */
++	client_pub += mlkem_buf_len;
++	if ((r = kex_nist_shared_key_ext(server_key, client_pub, ecdh_buf_len, buf)) < 0)
++		goto out;
++	if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0)
++		goto out;
++#ifdef DEBUG_KEXECDH
++	dump_digest("server public NIST:", server_pub, server_key_len);
++	dump_digest("server cipher text:", enc_out, enc_out_len);
++	dump_digest("server kem key:", secret, sizeof(secret));
++	dump_digest("concatenation of KEM key and ECDH shared key:",
++	    sshbuf_ptr(buf), sshbuf_len(buf));
++#endif
++	/* string-encoded hash is resulting shared secret */
++	sshbuf_reset(buf);
++	if ((r = sshbuf_put_string(buf, hash,
++	    ssh_digest_bytes(kex->hash_alg))) != 0)
++		goto out;
++#ifdef DEBUG_KEXECDH
++	dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
++#endif
++	/* success */
++	r = 0;
++	*server_blobp = server_blob;
++	*shared_secretp = buf;
++	server_blob = NULL;
++	buf = NULL;
++ out:
++	explicit_bzero(hash, sizeof(hash));
++	EVP_PKEY_free(server_key);
++	explicit_bzero(enc_out, sizeof(enc_out));
++	explicit_bzero(secret, sizeof(secret));
++	sshbuf_free(server_blob);
++	sshbuf_free(buf);
++	return r;
++}
++
++static int
++kex_kem_mlkem_nist_dec(struct kex *kex,
++    const struct sshbuf *server_blob, struct sshbuf **shared_secretp,
++    size_t mlkem_len)
++{
++	struct sshbuf *buf = NULL;
++	const u_char *ciphertext, *server_pub;
++	u_char hash[SSH_DIGEST_MAX_LENGTH];
++	u_char decap[crypto_kem_mlkem768_BYTES];
++	int r;
++	size_t nist_len;
++
++	*shared_secretp = NULL;
++
++	if (sshbuf_len(server_blob) < mlkem_len) {
++		r = SSH_ERR_SIGNATURE_INVALID;
++		goto out;
++	}
++
++	nist_len = sshbuf_len(server_blob) - mlkem_len;
++
++	switch (mlkem_len) {
++		case crypto_kem_mlkem768_CIPHERTEXTBYTES:
++			if (nist_len != NIST_P256_COMPRESSED_LEN
++				&& nist_len != NIST_P256_UNCOMPRESSED_LEN) {
++				r = SSH_ERR_SIGNATURE_INVALID;
++				goto out;
++			}
++		break;
++		case crypto_kem_mlkem1024_CIPHERTEXTBYTES:
++			if (nist_len != NIST_P384_COMPRESSED_LEN
++				&& nist_len != NIST_P384_UNCOMPRESSED_LEN) {
++				r = SSH_ERR_SIGNATURE_INVALID;
++				goto out;
++			}
++		break;
++	}
++
++	ciphertext = sshbuf_ptr(server_blob);
++	server_pub = ciphertext + mlkem_len;
++	/* hash concatenation of KEM key and ECDH shared key */
++	if ((buf = sshbuf_new()) == NULL) {
++		r = SSH_ERR_ALLOC_FAIL;
++		goto out;
++	}
++#ifdef DEBUG_KEXECDH
++	dump_digest("server cipher text:", ciphertext, mlkem_len);
++	dump_digest("server public key NIST:", server_pub, nist_len);
++#endif
++	r = (mlkem_len == crypto_kem_mlkem768_CIPHERTEXTBYTES) ?
++		mlkem768_decap_secret(kex->mlkem768_client_key, ciphertext, decap) :
++		mlkem1024_decap_secret(kex->mlkem1024_client_key, ciphertext, decap);
++
++	if (r != 0)
++		goto out;
++	if ((r = sshbuf_put(buf, decap, sizeof(decap))) != 0)
++		goto out;
++	if ((r = kex_nist_shared_key_ext(kex->ec_hybrid_client_key, server_pub,
++		nist_len, buf)) < 0)
++		goto out;
++	if ((r = ssh_digest_buffer(kex->hash_alg, buf,
++	    hash, sizeof(hash))) != 0)
++		goto out;
++#ifdef DEBUG_KEXECDH
++	dump_digest("client kem key:", decap, sizeof(decap));
++	dump_digest("concatenation of KEM key and ECDH shared key:",
++	    sshbuf_ptr(buf), sshbuf_len(buf));
++#endif
++	sshbuf_reset(buf);
++	if ((r = sshbuf_put_string(buf, hash,
++	    ssh_digest_bytes(kex->hash_alg))) != 0)
++		goto out;
++#ifdef DEBUG_KEXECDH
++	dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
++#endif
++	/* success */
++	r = 0;
++	*shared_secretp = buf;
++	buf = NULL;
++ out:
++	explicit_bzero(hash, sizeof(hash));
++	explicit_bzero(decap, sizeof(decap));
++	sshbuf_free(buf);
++	return r;
++}
++
++int
++kex_kem_mlkem768nistp256_keypair(struct kex *kex)
++{
++	return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem768_PUBLICKEYBYTES, NIST_P256_UNCOMPRESSED_LEN);
++}
++
++int
++kex_kem_mlkem768nistp256_enc(struct kex *kex, const struct sshbuf *client_blob,
++    struct sshbuf **server_blobp, struct sshbuf **shared_secretp)
++{
++	return kex_kem_mlkem_nist_enc(kex, "P-256", client_blob, server_blobp, shared_secretp);
++}
++
++int
++kex_kem_mlkem768nistp256_dec(struct kex *kex, const struct sshbuf *server_blob,
++    struct sshbuf **shared_secretp)
++{
++	return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp,
++		crypto_kem_mlkem768_CIPHERTEXTBYTES);
++}
++
++int
++kex_kem_mlkem1024nistp384_keypair(struct kex *kex)
++{
++	return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem1024_PUBLICKEYBYTES, NIST_P384_UNCOMPRESSED_LEN);
++}
++
++int
++kex_kem_mlkem1024nistp384_enc(struct kex *kex, const struct sshbuf *client_blob,
++    struct sshbuf **server_blobp, struct sshbuf **shared_secretp)
++{
++	return kex_kem_mlkem_nist_enc(kex, "P-384", client_blob, server_blobp, shared_secretp);
++}
++
++int
++kex_kem_mlkem1024nistp384_dec(struct kex *kex, const struct sshbuf *server_blob,
++    struct sshbuf **shared_secretp)
++{
++	return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp,
++		crypto_kem_mlkem1024_CIPHERTEXTBYTES);
++}
++
+ #else /* USE_MLKEM768X25519 */
+ int
+ kex_kem_mlkem768x25519_keypair(struct kex *kex)
+@@ -550,4 +1149,39 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
+ {
+ 	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
+ }
++
++int	 kex_kem_mlkem768nistp256_keypair(struct kex *)
++{
++	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int	 kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *,
++    struct sshbuf **, struct sshbuf **)
++{
++	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int	 kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *,
++    struct sshbuf **)
++{
++	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int	 kex_kem_mlkem1024nistp384_keypair(struct kex *)
++{
++	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int	 kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *,
++    struct sshbuf **, struct sshbuf **)
++{
++	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
++int	 kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *,
++    struct sshbuf **)
++{
++	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
++}
++
+ #endif /* USE_MLKEM768X25519 */
+diff --git a/monitor.c b/monitor.c
+index 0393c71aa..63180e2a9 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -2024,6 +2024,8 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
+ 	kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ 	kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ 	kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
++	kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
++	kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
+ 	kex->load_host_public_key=&get_hostkey_public_by_type;
+ 	kex->load_host_private_key=&get_hostkey_private_by_type;
+ 	kex->host_key_index=&get_hostkey_index;
+diff --git a/myproposal.h b/myproposal.h
+index 6433e0820..51e3ef5c8 100644
+--- a/myproposal.h
++++ b/myproposal.h
+@@ -26,6 +26,8 @@
+ 
+ #define KEX_SERVER_KEX	\
+ 	"mlkem768x25519-sha256," \
++	"mlkem768nistp256-sha256," \
++	"mlkem1024nistp384-sha384," \
+ 	"sntrup761x25519-sha512," \
+ 	"sntrup761x25519-sha512@openssh.com," \
+ 	"curve25519-sha256," \
+@@ -99,6 +101,8 @@
+ 	"aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se," \
+ 	"aes128-gcm@openssh.com,aes256-gcm@openssh.com"
+ #define KEX_DEFAULT_KEX_FIPS		\
++	"mlkem768nistp256-sha256," \
++	"mlkem1024nistp384-sha384," \
+ 	"ecdh-sha2-nistp256," \
+ 	"ecdh-sha2-nistp384," \
+ 	"ecdh-sha2-nistp521," \
+diff --git a/regress/unittests/kex/test_kex.c b/regress/unittests/kex/test_kex.c
+index f4700deeb..99a8e3b46 100644
+--- a/regress/unittests/kex/test_kex.c
++++ b/regress/unittests/kex/test_kex.c
+@@ -171,6 +171,8 @@ do_kex_with_key(char *kex, char *cipher, char *mac,
+ 	server2->kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ 	server2->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ 	server2->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
++	server2->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
++	server2->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
+ 	server2->kex->load_host_public_key = server->kex->load_host_public_key;
+ 	server2->kex->load_host_private_key = server->kex->load_host_private_key;
+ 	server2->kex->sign = server->kex->sign;
+@@ -248,6 +250,8 @@ kex_tests(void)
+ 	}
+ # ifdef USE_MLKEM768X25519
+ 	do_kex("mlkem768x25519-sha256");
++	do_kex("mlkem768nistp256-sha256");
++	do_kex("mlkem1024nistp384-sha384");
+ # endif /* USE_MLKEM768X25519 */
+ # ifdef USE_SNTRUP761X25519
+ 	do_kex("sntrup761x25519-sha512");
+diff --git a/ssh-keyscan.c b/ssh-keyscan.c
+index 1f15f3077..ece85c62f 100644
+--- a/ssh-keyscan.c
++++ b/ssh-keyscan.c
+@@ -306,6 +306,8 @@ keygrab_ssh2(con *c)
+ 	c->c_ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
+ 	c->c_ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
+ 	c->c_ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
++	c->c_ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
++	c->c_ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
+ 	ssh_set_verify_host_key_callback(c->c_ssh, key_print_wrapper);
+ 	/*
+ 	 * do the key-exchange until an error occurs or until
+diff --git a/ssh_api.c b/ssh_api.c
+index 38ac17da1..b178582b3 100644
+--- a/ssh_api.c
++++ b/ssh_api.c
+@@ -135,6 +135,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params)
+ 		ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ 		ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ 		ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
++		ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
++		ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
+ 		ssh->kex->load_host_public_key=&_ssh_host_public_key;
+ 		ssh->kex->load_host_private_key=&_ssh_host_private_key;
+ 		ssh->kex->sign=&_ssh_host_key_sign;
+@@ -154,6 +156,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params)
+ 		ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
+ 		ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
+ 		ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
++		ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
++		ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
+ 		ssh->kex->verify_host_key =&_ssh_verify_host_key;
+ 	}
+ 	*sshp = ssh;
+diff --git a/sshconnect2.c b/sshconnect2.c
+index 0c82c7782..75d13f54e 100644
+--- a/sshconnect2.c
++++ b/sshconnect2.c
+@@ -352,6 +352,8 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
+ 	ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
+ 	ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
+ 	ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
++	ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
++	ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
+ 	ssh->kex->verify_host_key=&verify_host_key_callback;
+ 
+ #if defined(GSSAPI) && defined(WITH_OPENSSL)
+diff --git a/sshd-auth.c b/sshd-auth.c
+index 6a5cd0c52..c4df314ef 100644
+--- a/sshd-auth.c
++++ b/sshd-auth.c
+@@ -867,6 +867,8 @@ do_ssh2_kex(struct ssh *ssh)
+ 	kex->kex[KEX_C25519_SHA256] = kex_gen_server;
+ 	kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
+ 	kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
++	kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
++	kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
+ 	kex->load_host_public_key=&get_hostkey_public_by_type;
+ 	kex->load_host_private_key=&get_hostkey_private_by_type;
+ 	kex->host_key_index=&get_hostkey_index;
+-- 
+2.55.0
+

diff --git a/0047-support-authentication-indicators-in-GSSAPI.patch b/0047-support-authentication-indicators-in-GSSAPI.patch
deleted file mode 100644
index fd06bdf..0000000
--- a/0047-support-authentication-indicators-in-GSSAPI.patch
+++ /dev/null
@@ -1,487 +0,0 @@
-From 477e0dfe44d5e309a9357375582884220fde4acf Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Sun, 12 Apr 2026 12:23:51 +0200
-Subject: [PATCH 47/54] support authentication indicators in GSSAPI
-
-https://github.com/openssh/openssh-portable/pull/500
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- configure.ac    |   1 +
- gss-serv-krb5.c |  89 +++++++++++++++++++++++++++++++++++----
- gss-serv.c      | 108 ++++++++++++++++++++++++++++++++++++++++++++++--
- servconf.c      |  16 ++++++-
- servconf.h      |   2 +
- ssh-gss.h       |   7 ++++
- sshd_config.5   |  46 +++++++++++++++++++++
- 7 files changed, 256 insertions(+), 13 deletions(-)
-
-diff --git a/configure.ac b/configure.ac
-index 86f0e9e09..1388e5e72 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -5129,6 +5129,7 @@ AC_ARG_WITH([kerberos5],
- 		AC_CHECK_HEADERS([gssapi.h gssapi/gssapi.h])
- 		AC_CHECK_HEADERS([gssapi_krb5.h gssapi/gssapi_krb5.h])
- 		AC_CHECK_HEADERS([gssapi_generic.h gssapi/gssapi_generic.h])
-+		AC_CHECK_HEADERS([gssapi_ext.h gssapi/gssapi_ext.h])
- 
- 		AC_SEARCH_LIBS([k_hasafs], [kafs], [AC_DEFINE([USE_AFS], [1],
- 			[Define this if you want to use libkafs' AFS support])])
-diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
-index 723509409..fc7cabc09 100644
---- a/gss-serv-krb5.c
-+++ b/gss-serv-krb5.c
-@@ -43,6 +43,7 @@
- #include "log.h"
- #include "misc.h"
- #include "servconf.h"
-+#include "match.h"
- 
- #include "ssh-gss.h"
- 
-@@ -87,6 +88,33 @@ ssh_gssapi_krb5_init(void)
- 	return 1;
- }
- 
-+/* Check if any of the indicators in the Kerberos ticket match
-+ * one of indicators in the list of allowed/denied rules.
-+ * In case of the match, apply the decision from the rule.
-+ * In case of no indicator from the ticket matching the rule, deny
-+ */
-+
-+static int
-+ssh_gssapi_check_indicators(ssh_gssapi_client *client, int *matched)
-+{
-+	int ret;
-+	u_int i;
-+	*matched = -1;
-+
-+	/* Check indicators */
-+	for (i = 0; client->indicators[i] != NULL; i++) {
-+		ret = match_pattern_list(client->indicators[i],
-+					 options.gss_indicators, 1);
-+		/* negative or positive match */
-+		if (ret != 0) {
-+			*matched = i;
-+			return ret;
-+		}
-+	}
-+	/* No rule matched */
-+	return 0;
-+}
-+
- /* Check if this user is OK to login. This only works with krb5 - other
-  * GSSAPI mechanisms will need their own.
-  * Returns true if the user is OK to log in, otherwise returns 0
-@@ -193,15 +221,15 @@ static int
- ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
- {
- 	krb5_principal princ;
--	int retval;
-+	int retval, matched, success;
- 	const char *errmsg;
- 	int k5login_exists;
- 
- 	if (ssh_gssapi_krb5_init() == 0)
- 		return 0;
- 
--	if ((retval = krb5_parse_name(krb_context, client->exportedname.value,
--	    &princ))) {
-+	retval = krb5_parse_name(krb_context, client->exportedname.value, &princ);
-+	if (retval) {
- 		errmsg = krb5_get_error_message(krb_context, retval);
- 		logit("krb5_parse_name(): %.100s", errmsg);
- 		krb5_free_error_message(krb_context, errmsg);
-@@ -216,17 +244,60 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
- 	if (k5login_exists &&
- 	    ssh_krb5_kuserok(krb_context, princ, name, k5login_exists)) {
- 		retval = 1;
--		logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
--		    name, (char *)client->displayname.value);
-+		errmsg = "krb5_kuserok";
- 	} else if (ssh_gssapi_krb5_cmdok(princ, client->exportedname.value,
- 		name, k5login_exists)) {
- 		retval = 1;
--		logit("Authorized to %s, krb5 principal %s "
--		    "(ssh_gssapi_krb5_cmdok)",
--		    name, (char *)client->displayname.value);
--	} else
-+		errmsg = "ssh_gssapi_krb5_cmdok";
-+	} else {
- 		retval = 0;
-+		goto out;
-+	}
- 
-+	/* At this point we are good if no indicators were defined */
-+	if (options.gss_indicators == NULL) {
-+		retval = 1;
-+		goto out;
-+	}
-+
-+	/* At this point we have indicators defined in the configuration,
-+	 * if clientt did not provide any indicators, we reject */
-+	if (!client->indicators) {
-+		retval = 0;
-+		logit("GSSAPI authentication indicators enforced "
-+		      "but indicators not provided by the client. "
-+		      "krb5 principal %s denied",
-+		      (char *)client->displayname.value);
-+		goto out;
-+	}
-+
-+	/* At this point the configuration enforces presence of indicators
-+	 * check the match */
-+	matched = -1;
-+	success = ssh_gssapi_check_indicators(client, &matched);
-+
-+	switch (success) {
-+	case 1:
-+		logit("Provided indicator %s allowed by the configuration",
-+		      client->indicators[matched]);
-+		retval = 1;
-+		break;
-+	case -1:
-+		logit("Provided indicator %s rejected by the configuration",
-+		      client->indicators[matched]);
-+		retval = 0;
-+		break;
-+	default:
-+		logit("Provided indicators do not match the configuration");
-+		retval = 0;
-+		break;
-+	}
-+
-+out:
-+	if (retval == 1) {
-+		logit("Authorized to %s, krb5 principal %s (%s)",
-+		      name, (char *)client->displayname.value, errmsg);
-+	}
- 	krb5_free_principal(krb_context, princ);
- 	return retval;
- }
-diff --git a/gss-serv.c b/gss-serv.c
-index 6eb7d2163..686ac4eb3 100644
---- a/gss-serv.c
-+++ b/gss-serv.c
-@@ -55,7 +55,7 @@ extern ServerOptions options;
- 
- static ssh_gssapi_client gssapi_client =
-     { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
--    GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0};
-+    GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL};
- 
- ssh_gssapi_mech gssapi_null_mech =
-     { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
-@@ -295,6 +295,99 @@ ssh_gssapi_parse_ename(Gssctxt *ctx, gss
- 	return GSS_S_COMPLETE;
- }
- 
-+
-+/* Extract authentication indicators from the Kerberos ticket. Authentication
-+ * indicators are GSSAPI name attributes for the name "auth-indicators".
-+ * Multiple indicators might be present in the ticket.
-+ * Each indicator is an utf8 string. */
-+
-+#define AUTH_INDICATORS_TAG "auth-indicators"
-+#define SSH_GSSAPI_MAX_INDICATORS 64
-+
-+/* Privileged (called from accept_secure_ctx) */
-+static OM_uint32
-+ssh_gssapi_getindicators(Gssctxt *ctx, gss_name_t gss_name, ssh_gssapi_client *client)
-+{
-+	gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET;
-+	gss_buffer_desc value = GSS_C_EMPTY_BUFFER;
-+	gss_buffer_desc display_value = GSS_C_EMPTY_BUFFER;
-+	int is_mechname, authenticated, complete, more;
-+	size_t count, i;
-+
-+	/* always initialize client->indicators */
-+	client->indicators = NULL;
-+
-+	ctx->major = gss_inquire_name(&ctx->minor, gss_name,
-+				      &is_mechname, NULL, &attrs);
-+	if (ctx->major != GSS_S_COMPLETE)
-+		return ctx->major;
-+
-+	if (attrs == GSS_C_NO_BUFFER_SET) {
-+		/* no indicators in the ticket */
-+		return GSS_S_COMPLETE;
-+	}
-+
-+	/* client->indicators is NULL terminated */
-+	count = 0;
-+	client->indicators = xcalloc(count + 1, sizeof(char *));
-+
-+	for (i = 0; i < attrs->count; i++) {
-+		authenticated = 0;
-+		complete = 0;
-+		more = -1;
-+
-+		/* skip anything but auth-indicators */
-+		if (((sizeof(AUTH_INDICATORS_TAG) - 1) != attrs->elements[i].length) ||
-+		    memcmp(AUTH_INDICATORS_TAG, attrs->elements[i].value,
-+			   sizeof(AUTH_INDICATORS_TAG) - 1) != 0)
-+			continue;
-+
-+		/* retrieve all indicators */
-+		while (more != 0) {
-+			value.value = NULL;
-+			display_value.value = NULL;
-+
-+			ctx->major = gss_get_name_attribute(&ctx->minor, gss_name,
-+							    &attrs->elements[i],
-+							    &authenticated, &complete,
-+							    &value, &display_value, &more);
-+			if (ctx->major != GSS_S_COMPLETE)
-+				goto out;
-+
-+			if (value.value == NULL || !authenticated)
-+				continue;
-+
-+			if (count >= SSH_GSSAPI_MAX_INDICATORS) {
-+				logit("ssh_gssapi_getindicators:"
-+				      " too many indicators, truncating at %d",
-+				      SSH_GSSAPI_MAX_INDICATORS);
-+				goto out;
-+			}
-+
-+			client->indicators[count] = xmalloc(value.length + 1);
-+			memcpy(client->indicators[count], value.value, value.length);
-+			client->indicators[count][value.length] = '\0';
-+			count++;
-+
-+			/* add NULL terminator */
-+			client->indicators = xrecallocarray(client->indicators, count,
-+							    count + 1, sizeof(char *));
-+		}
-+	}
-+
-+out:
-+	if (ctx->major != GSS_S_COMPLETE && client->indicators != NULL) {
-+		for (i = 0; i < count; i++)
-+			free(client->indicators[i]);
-+		free(client->indicators);
-+		client->indicators = NULL;
-+	}
-+	gss_release_buffer(&ctx->minor, &value);
-+	gss_release_buffer(&ctx->minor, &display_value);
-+	gss_release_buffer_set(&ctx->minor, &attrs);
-+	return ctx->major;
-+}
-+
- /* Extract the client details from a given context. This can only reliably
-  * be called once for a context */
- 
-@@ -386,6 +475,12 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
- 	}
- 
- 	gss_release_buffer(&ctx->minor, &ename);
-+	/* Retrieve authentication indicators, if they exist */
-+	if ((ctx->major = ssh_gssapi_getindicators(ctx,
-+	    ctx->client, client))) {
-+		ssh_gssapi_error(ctx);
-+		return (ctx->major);
-+	}
- 
- 	/* We can't copy this structure, so we just move the pointer to it */
- 	client->creds = ctx->client_creds;
-@@ -453,6 +548,7 @@ int
- ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
- {
- 	OM_uint32 lmin;
-+	size_t i;
- 
- 	(void) kex; /* used in privilege separation */
- 
-@@ -471,8 +567,14 @@ ssh_gssapi_userok(char *user, struct passwd *pw, int kex)
- 			gss_release_buffer(&lmin, &gssapi_client.displayname);
- 			gss_release_buffer(&lmin, &gssapi_client.exportedname);
- 			gss_release_cred(&lmin, &gssapi_client.creds);
--			explicit_bzero(&gssapi_client,
--			    sizeof(ssh_gssapi_client));
-+
-+			if (gssapi_client.indicators != NULL) {
-+				for (i = 0; gssapi_client.indicators[i] != NULL; i++)
-+					free(gssapi_client.indicators[i]);
-+				free(gssapi_client.indicators);
-+			}
-+
-+			explicit_bzero(&gssapi_client, sizeof(ssh_gssapi_client));
- 			return 0;
- 		}
- 	else
-diff --git a/servconf.c b/servconf.c
-index 6c1ba77f6..268c09d1c 100644
---- a/servconf.c
-+++ b/servconf.c
-@@ -145,6 +145,7 @@ initialize_server_options(ServerOptions *options)
- 	options->gss_cleanup_creds = -1;
- 	options->gss_deleg_creds = -1;
- 	options->gss_strict_acceptor = -1;
-+	options->gss_indicators = NULL;
- 	options->gss_store_rekey = -1;
- 	options->gss_kex_algorithms = NULL;
- 	options->use_kuserok = -1;
-@@ -560,6 +561,7 @@ fill_default_server_options(ServerOptions *options)
- 	CLEAR_ON_NONE(options->routing_domain);
- 	CLEAR_ON_NONE(options->host_key_agent);
- 	CLEAR_ON_NONE(options->per_source_penalty_exempt);
-+	CLEAR_ON_NONE(options->gss_indicators);
- 
- 	for (i = 0; i < options->num_host_key_files; i++)
- 		CLEAR_ON_NONE(options->host_key_files[i]);
-@@ -599,7 +601,7 @@ typedef enum {
- 	sPerSourcePenalties, sPerSourcePenaltyExemptList,
- 	sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
- 	sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssEnablek5users, sGssStrictAcceptor,
--	sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
-+	sGssIndicators, sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
- 	sAcceptEnv, sSetEnv, sPermitTunnel,
- 	sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
- 	sUsePrivilegeSeparation, sAllowAgentForwarding,
-@@ -696,6 +698,7 @@ static struct {
- 	{ "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL },
- 	{ "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
- 	{ "gssapienablek5users", sGssEnablek5users, SSHCFG_ALL },
-+	{ "gssapiindicators", sGssIndicators, SSHCFG_ALL },
- #else
- 	{ "gssapiauthentication", sUnsupported, SSHCFG_ALL },
- 	{ "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
-@@ -706,6 +709,7 @@ static struct {
- 	{ "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapienablek5users", sUnsupported, SSHCFG_ALL },
-+	{ "gssapiindicators", sUnsupported, SSHCFG_ALL },
- #endif
- 	{ "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
-@@ -1737,6 +1741,15 @@ process_server_config_line_depth(ServerOptions *options, char *line,
- 			options->gss_kex_algorithms = xstrdup(arg);
- 		break;
- 
-+	case sGssIndicators:
-+		arg = argv_next(&ac, &av);
-+		if (!arg || *arg == '\0')
-+			fatal("%s line %d: %s missing argument.",
-+			    filename, linenum, keyword);
-+		if (options->gss_indicators == NULL)
-+			options->gss_indicators = xstrdup(arg);
-+		break;
-+
- 	case sPasswordAuthentication:
- 		intptr = &options->password_authentication;
- 		goto parse_flag;
-@@ -3383,6 +3396,7 @@ dump_config(ServerOptions *o)
- 	dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor);
- 	dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey);
- 	dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms);
-+	dump_cfg_string(sGssIndicators, o->gss_indicators);
- #endif
- 	dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
- 	dump_cfg_fmtint(sKbdInteractiveAuthentication,
-diff --git a/servconf.h b/servconf.h
-index 569100e47..781f20c61 100644
---- a/servconf.h
-+++ b/servconf.h
-@@ -181,6 +181,7 @@ typedef struct {
- 	char   **allow_groups;
- 	u_int num_deny_groups;
- 	char   **deny_groups;
-+	char   *gss_indicators;
- 
- 	u_int num_subsystems;
- 	char   **subsystem_name;
-@@ -310,6 +311,7 @@ TAILQ_HEAD(include_list, include_item);
- 		M_CP_STROPT(routing_domain); \
- 		M_CP_STROPT(permit_user_env_allowlist); \
- 		M_CP_STROPT(pam_service_name); \
-+		M_CP_STROPT(gss_indicators); \
- 		M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files, 1);\
- 		M_CP_STRARRAYOPT(revoked_keys_files, \
- 		    num_revoked_keys_files, 1); \
-diff --git a/ssh-gss.h b/ssh-gss.h
-index 329dc9da0..1506719a9 100644
---- a/ssh-gss.h
-+++ b/ssh-gss.h
-@@ -34,6 +34,12 @@
- #include <gssapi/gssapi.h>
- #endif
- 
-+#ifdef HAVE_GSSAPI_EXT_H
-+#include <gssapi_ext.h>
-+#elif defined(HAVE_GSSAPI_GSSAPI_EXT_H)
-+#include <gssapi/gssapi_ext.h>
-+#endif
-+
- #ifdef KRB5
- # ifndef HEIMDAL
- #  ifdef HAVE_GSSAPI_GENERIC_H
-@@ -112,6 +118,7 @@ typedef struct {
- 	ssh_gssapi_ccache store;
- 	int used;
- 	int updated;
-+	char **indicators; /* auth indicators */
- } ssh_gssapi_client;
- 
- typedef struct ssh_gssapi_mech_struct {
-diff --git a/sshd_config.5 b/sshd_config.5
-index 1d16dabaf..4ca80016c 100644
---- a/sshd_config.5
-+++ b/sshd_config.5
-@@ -803,6 +803,52 @@ This option only applies to connections using GSSAPI.
- .Pp
- The list of supported key exchange algorithms may also be obtained using
- .Qq ssh -Q GSSAPIKexAlgorithms .
-+.It Cm GSSAPIIndicators
-+Specifies whether to accept or deny GSSAPI authenticated access if Kerberos
-+mechanism is used and Kerberos ticket contains a particular set of
-+authentication indicators. The values can be specified as a comma-separated list
-+.Cm [!]name1,[!]name2,... .
-+When indicator's name is prefixed with !, the authentication indicator 'name'
-+will deny access to the system. Otherwise, one of non-negated authentication
-+indicators must be present in the Kerberos ticket to allow access. If
-+.Cm GSSAPIIndicators
-+is defined, a Kerberos ticket that has indicators but does not match the
-+policy will get denial. If at least one indicator is configured, whether for
-+access or denial, tickets without authentication indicators will be explicitly
-+rejected.
-+.Pp
-+By default systems using MIT Kerberos 1.17 or later will not assign any
-+indicators. SPAKE and PKINIT methods add authentication indicators
-+to all successful authentications. The SPAKE pre-authentication method is
-+preferred over an encrypted timestamp pre-authentication when passwords used to
-+authenticate user principals. Kerberos KDCs built with Heimdal Kerberos
-+(including Samba AD DC built with Heimdal) do not add authentication
-+indicators. However, OpenSSH built against Heimdal Kerberos library is able to
-+inquire authentication indicators and thus can be used to check for their presence.
-+.Pp
-+Indicator name is case-sensitive and depends on the configuration of a
-+particular Kerberos deployment. Indicators available in MIT Kerberos and
-+FreeIPA environments:
-+.Pp
-+.Bl -tag -width XXXX -offset indent -compact
-+.It Cm hardened
-+SPAKE or encrypted timestamp pre-authentication mechanisms in MIT Kerberos and FreeIPA
-+.It Cm pkinit
-+smartcard or PKCS11 token-based pre-authentication in MIT Kerberos and FreeIPA
-+.It Cm radius
-+pre-authentication based on a RADIUS server in MIT Kerberos and FreeIPA
-+.It Cm otp
-+TOTP/HOTP-based two-factor pre-authentication in FreeIPA
-+.It Cm idp
-+OAuth2-based pre-authentication in FreeIPA using an external identity provider
-+and device authorization grant flow
-+.It Cm passkey
-+FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens
-+.El
-+.Pp
-+The default
-+.Dq none
-+is to not use GSSAPI authentication indicators for access decisions.
- .It Cm HostbasedAcceptedAlgorithms
- The default is handled system-wide by
- .Xr crypto-policies 7 .
--- 
-2.53.0
-

diff --git a/0048-NIST-curves-hybrid-KEX-implementation.patch b/0048-NIST-curves-hybrid-KEX-implementation.patch
deleted file mode 100644
index c7ab711..0000000
--- a/0048-NIST-curves-hybrid-KEX-implementation.patch
+++ /dev/null
@@ -1,1303 +0,0 @@
-From 3802a51b69044f419bb53deaff7061d62d9596fe Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 20 Oct 2025 16:07:31 +0200
-Subject: [PATCH 48/54] NIST curves hybrid KEX implementation
-
----
- compat.c                         |  23 +-
- compat.h                         |   1 +
- crypto_api.h                     |   4 +
- kex-names.c                      |  89 +++-
- kex.c                            |   1 +
- kex.h                            |  19 +
- kexgen.c                         |  56 ++-
- kexmlkem768x25519.c              | 679 +++++++++++++++++++++++++++++--
- monitor.c                        |   2 +
- myproposal.h                     |   4 +
- regress/unittests/kex/test_kex.c |   4 +
- ssh-keyscan.c                    |   2 +
- ssh_api.c                        |   4 +
- sshconnect2.c                    |   2 +
- sshd-auth.c                      |   2 +
- 15 files changed, 851 insertions(+), 41 deletions(-)
-
-diff --git a/compat.c b/compat.c
-index 4312510e5..e6c7935d4 100644
---- a/compat.c
-+++ b/compat.c
-@@ -36,6 +36,7 @@
- #include "compat.h"
- #include "log.h"
- #include "match.h"
-+#include <openssl/fips.h>
- 
- /* determine bug flags from SSH protocol banner */
- void
-@@ -148,8 +149,9 @@ char *
- compat_kex_proposal(struct ssh *ssh, const char *p)
- {
- 	char *cp = NULL, *cp2 = NULL;
-+	int mlkem_available = is_mlkem768_available();
- 
--	if ((ssh->compat & (SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX)) == 0)
-+	if ((ssh->compat & (SSH_BUG_CURVE25519PAD|SSH_OLD_DHGEX)) == 0 && mlkem_available == 2)
- 		return xstrdup(p);
- 	debug2_f("original KEX proposal: %s", p);
- 	if ((ssh->compat & SSH_BUG_CURVE25519PAD) != 0)
-@@ -164,6 +166,25 @@ compat_kex_proposal(struct ssh *ssh, const char *p)
- 		free(cp);
- 		cp = cp2;
- 	}
-+	if (mlkem_available == 2)
-+		return cp ? cp : xstrdup(p);
-+	if (mlkem_available == 1 && FIPS_mode()) {
-+		if ((cp2 = match_filter_denylist(cp ? cp : p,
-+		    "mlkem768x25519-sha256")) == NULL)
-+			fatal("match_filter_denylist failed");
-+		free(cp);
-+		cp = cp2;
-+	}
-+	if (mlkem_available == 0) {
-+		if ((cp2 = match_filter_denylist(cp ? cp : p,
-+		    "mlkem768x25519-sha256,"
-+		    "mlkem768nistp256-sha256,"
-+		    "mlkem1024nistp384-sha384")) == NULL)
-+			fatal("match_filter_denylist failed");
-+		free(cp);
-+		cp = cp2;
-+	}
-+
- 	if (cp == NULL || *cp == '\0')
- 		fatal("No supported key exchange algorithms found");
- 	debug2_f("compat KEX proposal: %s", cp);
-diff --git a/compat.h b/compat.h
-index 2e6db5bf9..b78e55b69 100644
---- a/compat.h
-+++ b/compat.h
-@@ -62,4 +62,5 @@ struct ssh;
- 
- void    compat_banner(struct ssh *, const char *);
- char	*compat_kex_proposal(struct ssh *, const char *);
-+int is_mlkem768_available(void);
- #endif
-diff --git a/crypto_api.h b/crypto_api.h
-index f5e38b547..6a8ede067 100644
---- a/crypto_api.h
-+++ b/crypto_api.h
-@@ -82,4 +82,8 @@ int	crypto_kem_sntrup761_keypair(unsigned char *pk, unsigned char *sk);
- #define crypto_kem_mlkem768_CIPHERTEXTBYTES 1088
- #define crypto_kem_mlkem768_BYTES 32
- 
-+#define crypto_kem_mlkem1024_PUBLICKEYBYTES 1568
-+#define crypto_kem_mlkem1024_SECRETKEYBYTES 3168
-+#define crypto_kem_mlkem1024_CIPHERTEXTBYTES 1568
-+
- #endif /* crypto_api_h */
-diff --git a/kex-names.c b/kex-names.c
-index 882f2905b..60fc487b0 100644
---- a/kex-names.c
-+++ b/kex-names.c
-@@ -34,6 +34,7 @@
- #include <openssl/crypto.h>
- #include <openssl/fips.h>
- #include <openssl/evp.h>
-+#include <openssl/err.h>
- #endif
- 
- #include "kex.h"
-@@ -90,6 +91,10 @@ static const struct kexalg kexalgs[] = {
- #ifdef USE_MLKEM768X25519
- 	{ KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0,
- 	    SSH_DIGEST_SHA256, KEX_IS_PQ },
-+	{ KEX_MLKEM768NISTP256_SHA256, KEX_KEM_MLKEM768NISTP256_SHA256, 0,
-+	    SSH_DIGEST_SHA256, KEX_IS_PQ },
-+	{ KEX_MLKEM1024NISTP384_SHA384, KEX_KEM_MLKEM1024NISTP384_SHA384, 0,
-+	    SSH_DIGEST_SHA384, KEX_IS_PQ },
- #endif
- #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */
- 	{ NULL, 0, -1, -1, 0 },
-@@ -108,14 +113,27 @@ static const struct kexalg gss_kexalgs[] = {
- 	{ NULL, 0, -1, -1, 0},
- };
- 
--static int is_mlkem768_available()
-+/*
-+ * 0 - unavailable
-+ * 1 - available in non-FIPS mode
-+ * 2 - available always
-+ */
-+int is_mlkem768_available()
- {
- 	static int is_fetched = -1;
- 
- 	if (is_fetched == -1) {
--		EVP_KEM *mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", NULL);
--		is_fetched = mlkem768 != NULL ? 1 : 0;
-+		EVP_KEM *mlkem768 = NULL;
-+
-+		ERR_set_mark();
-+		mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", NULL);
-+		is_fetched = (mlkem768 == NULL) ? 0 : 2;
-+		if (is_fetched == 0 && FIPS_mode() == 1) {
-+		    mlkem768 = EVP_KEM_fetch(NULL, "mlkem768", "provider=default,-fips");
-+		    is_fetched = (mlkem768 == NULL) ? 0 : 1;
-+		}
- 		EVP_KEM_free(mlkem768);
-+		ERR_pop_to_mark();
- 	}
- 
- 	return is_fetched;
-@@ -127,11 +145,32 @@ kex_alg_list_internal(char sep, const struct kexalg *algs)
- 	char *ret = NULL;
- 	const struct kexalg *k;
- 	char sep_str[2] = {sep, '\0'};
-+	int x25519mlkem_available = 0, nistmlkem_available = 0;
- 
--	for (k = kexalgs; k->name != NULL; k++) {
--		if (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0
--			&& !is_mlkem768_available())
-+	/*
-+	 * FIPS provider can provide ML-KEMs and then all hybrids are available
-+	 * Otherwise only NIST hybrids are available
-+	 * */
-+	if (FIPS_mode()) {
-+	    if (is_mlkem768_available() == 2) {
-+	        x25519mlkem_available = 1;
-+	        nistmlkem_available = 1;
-+	    } else if (is_mlkem768_available() == 1) {
-+	        nistmlkem_available = 1;
-+	    }
-+	} else {
-+	    if (is_mlkem768_available() > 0) {
-+	        x25519mlkem_available = 1;
-+	        nistmlkem_available = 1;
-+	    }
-+	}
-+
-+	for (k = algs; k->name != NULL; k++) {
-+		if (  (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0    && x25519mlkem_available == 0)
-+		   || (strcmp(k->name, KEX_MLKEM768NISTP256_SHA256) == 0  && nistmlkem_available == 0)
-+		   || (strcmp(k->name, KEX_MLKEM1024NISTP384_SHA384) == 0 && nistmlkem_available == 0))
- 			continue;
-+
- 		xextendf(&ret, sep_str, "%s", k->name);
- 	}
- 
-@@ -154,10 +193,30 @@ static const struct kexalg *
- kex_alg_by_name(const char *name)
- {
- 	const struct kexalg *k;
-+	int x25519mlkem_available = 0, nistmlkem_available = 0;
- 
--	if (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0
--		&& !is_mlkem768_available())
--	return NULL;
-+	/*
-+	 * FIPS provider can provide ML-KEMs and then all hybrids are available
-+	 * Otherwise only NIST hybrids are available
-+	 * */
-+	if (FIPS_mode()) {
-+	    if (is_mlkem768_available() == 2) {
-+	        x25519mlkem_available = 1;
-+	        nistmlkem_available = 1;
-+	    } else if (is_mlkem768_available() == 1) {
-+	        nistmlkem_available = 1;
-+	    }
-+	} else {
-+	    if (is_mlkem768_available() > 0) {
-+	        x25519mlkem_available = 1;
-+	        nistmlkem_available = 1;
-+	    }
-+	}
-+
-+	if (  (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0    && x25519mlkem_available == 0)
-+	   || (strcmp(name, KEX_MLKEM768NISTP256_SHA256) == 0  && nistmlkem_available == 0)
-+	   || (strcmp(name, KEX_MLKEM1024NISTP384_SHA384) == 0 && nistmlkem_available == 0))
-+	   return NULL;
- 
- 	for (k = kexalgs; k->name != NULL; k++) {
- 		if (strcmp(k->name, name) == 0)
-@@ -229,8 +288,16 @@ kex_names_valid(const char *names)
- 			return 0;
- 		}
- 		if (kex_alg_by_name(p) == NULL) {
--			if (FIPS_mode())
--				error("\"%.100s\" is not allowed in FIPS mode", p);
-+			if (FIPS_mode()) {
-+				if ((strcmp(p, KEX_MLKEM768X25519_SHA256) == 0)
-+				    || (strcmp(p, KEX_MLKEM768NISTP256_SHA256) == 0)
-+				    || (strcmp(p, KEX_MLKEM1024NISTP384_SHA384) == 0)) {
-+					debug("\"%.100s\" is not allowed in FIPS mode", p);
-+					continue;
-+				}
-+				else
-+					error("\"%.100s\" is not allowed in FIPS mode", p);
-+			}
- 			else
- 				error("Unsupported KEX algorithm \"%.100s\"", p);
- 			free(s);
-diff --git a/kex.c b/kex.c
-index 1eb3ca9d3..8cfd446c0 100644
---- a/kex.c
-+++ b/kex.c
-@@ -757,6 +757,7 @@ kex_free(struct kex *kex)
- #ifdef OPENSSL_HAS_ECC
- 	EC_KEY_free(kex->ec_client_key);
- #endif /* OPENSSL_HAS_ECC */
-+	EVP_PKEY_free(kex->ec_hybrid_client_key);
- #endif /* WITH_OPENSSL */
- 	for (mode = 0; mode < MODE_MAX; mode++) {
- 		kex_free_newkeys(kex->newkeys[mode]);
-diff --git a/kex.h b/kex.h
-index ff41324e9..d110ad29a 100644
---- a/kex.h
-+++ b/kex.h
-@@ -72,6 +72,8 @@
- #define	KEX_SNTRUP761X25519_SHA512	"sntrup761x25519-sha512"
- #define	KEX_SNTRUP761X25519_SHA512_OLD	"sntrup761x25519-sha512@openssh.com"
- #define	KEX_MLKEM768X25519_SHA256	"mlkem768x25519-sha256"
-+#define	KEX_MLKEM768NISTP256_SHA256     "mlkem768nistp256-sha256"
-+#define	KEX_MLKEM1024NISTP384_SHA384    "mlkem1024nistp384-sha384"
- 
- #define COMP_NONE	0
- #define COMP_DELAYED	2
-@@ -110,6 +112,8 @@ enum kex_exchange {
- 	KEX_C25519_SHA256,
- 	KEX_KEM_SNTRUP761X25519_SHA512,
- 	KEX_KEM_MLKEM768X25519_SHA256,
-+	KEX_KEM_MLKEM768NISTP256_SHA256,
-+	KEX_KEM_MLKEM1024NISTP384_SHA384,
- #ifdef GSSAPI
- 	KEX_GSS_GRP1_SHA1,
- 	KEX_GSS_GRP14_SHA1,
-@@ -211,6 +215,9 @@ struct kex {
- 	u_char sntrup761_client_key[crypto_kem_sntrup761_SECRETKEYBYTES]; /* KEM */
- 	u_char mlkem768_client_key[crypto_kem_mlkem768_SECRETKEYBYTES]; /* KEM */
- 	struct sshbuf *client_pub;
-+	/* FIXME */
-+	EVP_PKEY *ec_hybrid_client_key; /* NIST hybrids */
-+	u_char mlkem1024_client_key[crypto_kem_mlkem1024_SECRETKEYBYTES]; /* ML-KEM 1024 + NIST */
- };
- 
- int	 kex_name_valid(const char *);
-@@ -293,6 +300,18 @@ int	 kex_kem_mlkem768x25519_enc(struct kex *, const struct sshbuf *,
- int	 kex_kem_mlkem768x25519_dec(struct kex *, const struct sshbuf *,
-     struct sshbuf **);
- 
-+int	 kex_kem_mlkem768nistp256_keypair(struct kex *);
-+int	 kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *,
-+    struct sshbuf **, struct sshbuf **);
-+int	 kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *,
-+    struct sshbuf **);
-+
-+int	 kex_kem_mlkem1024nistp384_keypair(struct kex *);
-+int	 kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *,
-+    struct sshbuf **, struct sshbuf **);
-+int	 kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *,
-+    struct sshbuf **);
-+
- int	 kex_dh_keygen(struct kex *);
- int	 kex_dh_compute_key(struct kex *, BIGNUM *, struct sshbuf *);
- 
-diff --git a/kexgen.c b/kexgen.c
-index 2f8252488..9a356e298 100644
---- a/kexgen.c
-+++ b/kexgen.c
-@@ -133,12 +133,24 @@ kex_gen_client(struct ssh *ssh)
- 		break;
- 	case KEX_KEM_MLKEM768X25519_SHA256:
- 		if (FIPS_mode()) {
--		    logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
--		    r = SSH_ERR_INVALID_ARGUMENT;
-+		    EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
-+		    if (mlkem == NULL) {
-+		        logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
-+		        r = SSH_ERR_INVALID_ARGUMENT;
-+		    } else {
-+			EVP_KEM_free(mlkem);
-+		        r = kex_kem_mlkem768x25519_keypair(kex);
-+		    }
- 		} else {
- 		    r = kex_kem_mlkem768x25519_keypair(kex);
- 		}
- 		break;
-+	case KEX_KEM_MLKEM768NISTP256_SHA256:
-+		    r = kex_kem_mlkem768nistp256_keypair(kex);
-+		break;
-+	case KEX_KEM_MLKEM1024NISTP384_SHA384:
-+		    r = kex_kem_mlkem1024nistp384_keypair(kex);
-+		break;
- 	default:
- 		r = SSH_ERR_INVALID_ARGUMENT;
- 		break;
-@@ -223,13 +235,28 @@ input_kex_gen_reply(int type, uint32_t seq, struct ssh *ssh)
- 		break;
- 	case KEX_KEM_MLKEM768X25519_SHA256:
- 		if (FIPS_mode()) {
--		    logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
--		    r = SSH_ERR_INVALID_ARGUMENT;
-+		    EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
-+		    if (mlkem == NULL) {
-+		        logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
-+		        r = SSH_ERR_INVALID_ARGUMENT;
-+		    } else {
-+			EVP_KEM_free(mlkem);
-+		        r = kex_kem_mlkem768x25519_dec(kex, server_blob,
-+		            &shared_secret);
-+		    }
- 		} else {
- 		    r = kex_kem_mlkem768x25519_dec(kex, server_blob,
- 		        &shared_secret);
- 		}
- 		break;
-+	case KEX_KEM_MLKEM768NISTP256_SHA256:
-+		    r = kex_kem_mlkem768nistp256_dec(kex, server_blob,
-+		        &shared_secret);
-+		break;
-+	case KEX_KEM_MLKEM1024NISTP384_SHA384:
-+		    r = kex_kem_mlkem1024nistp384_dec(kex, server_blob,
-+		        &shared_secret);
-+		break;
- 	default:
- 		r = SSH_ERR_INVALID_ARGUMENT;
- 		break;
-@@ -283,6 +310,8 @@ out:
- 	    sizeof(kex->sntrup761_client_key));
- 	explicit_bzero(kex->mlkem768_client_key,
- 	    sizeof(kex->mlkem768_client_key));
-+	explicit_bzero(kex->mlkem1024_client_key,
-+	    sizeof(kex->mlkem1024_client_key));
- 	sshbuf_free(server_host_key_blob);
- 	free(signature);
- 	sshbuf_free(tmp);
-@@ -362,13 +391,28 @@ input_kex_gen_init(int type, uint32_t seq, struct ssh *ssh)
- 		break;
- 	case KEX_KEM_MLKEM768X25519_SHA256:
- 		if (FIPS_mode()) {
--		    logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
--		    r = SSH_ERR_INVALID_ARGUMENT;
-+		    EVP_KEM *mlkem = EVP_KEM_fetch(NULL, "mlkem768", NULL);
-+		    if (mlkem == NULL) {
-+		        logit_f("Key exchange type mlkem768x25519 is not allowed in FIPS mode");
-+		        r = SSH_ERR_INVALID_ARGUMENT;
-+		    } else {
-+			EVP_KEM_free(mlkem);
-+		        r = kex_kem_mlkem768x25519_enc(kex, client_pubkey,
-+		            &server_pubkey, &shared_secret);
-+		    }
- 		} else {
- 		    r = kex_kem_mlkem768x25519_enc(kex, client_pubkey,
- 		        &server_pubkey, &shared_secret);
- 		}
- 		break;
-+	case KEX_KEM_MLKEM768NISTP256_SHA256:
-+		    r = kex_kem_mlkem768nistp256_enc(kex, client_pubkey,
-+		        &server_pubkey, &shared_secret);
-+		break;
-+	case KEX_KEM_MLKEM1024NISTP384_SHA384:
-+		    r = kex_kem_mlkem1024nistp384_enc(kex, client_pubkey,
-+		        &server_pubkey, &shared_secret);
-+		break;
- 	default:
- 		r = SSH_ERR_INVALID_ARGUMENT;
- 		break;
-diff --git a/kexmlkem768x25519.c b/kexmlkem768x25519.c
-index ab6167221..7c063f21c 100644
---- a/kexmlkem768x25519.c
-+++ b/kexmlkem768x25519.c
-@@ -44,19 +44,33 @@
- #ifdef USE_MLKEM768X25519
- 
- #include "libcrux_mlkem768_sha3.h"
-+#include <openssl/bn.h>
-+#include <openssl/ec.h>
- #include <openssl/err.h>
- #include <openssl/evp.h>
-+#include <openssl/fips.h>
- #include <stdio.h>
- 
-+#define FIPS_FALLBACK_PROPQ "provider=default,-fips"
-+
- static int
--mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
-+mlkem_keypair_gen(const char *algname, unsigned char *pubkeybuf, size_t pubkey_size,
-+	          unsigned char *privkeybuf, size_t privkey_size)
- {
-     EVP_PKEY_CTX *ctx = NULL;
-     EVP_PKEY *pkey = NULL;
-     int ret = SSH_ERR_INTERNAL_ERROR;
--    size_t pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES, privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES;
-+    size_t got_pub_size = pubkey_size, got_priv_size = privkey_size;
-+
-+    ctx = EVP_PKEY_CTX_new_from_name(NULL, algname, NULL);
-+
-+    if (ctx == NULL && FIPS_mode()) {
-+	/* We have filtered x25519 + ML-KEM in FIPS mode earlier
-+	 * so if we are in FIPS mode and ML-KEM is not available with default propq,
-+	 * we can fetch it from the default provider */
-+        ctx = EVP_PKEY_CTX_new_from_name(NULL, algname, FIPS_FALLBACK_PROPQ);
-+    }
- 
--    ctx = EVP_PKEY_CTX_new_from_name(NULL, "mlkem768", NULL);
-     if (ctx == NULL) {
- 	ret = SSH_ERR_LIBCRYPTO_ERROR;
- 	goto err;
-@@ -68,17 +82,23 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
-         goto err;
-     }
- 
--    if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &pubkey_size) <= 0
--	|| EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &privkey_size) <= 0) {
-+    if (EVP_PKEY_get_raw_public_key(pkey, NULL, &got_pub_size) <= 0
-+	|| EVP_PKEY_get_raw_private_key(pkey, NULL, &got_priv_size) <= 0) {
- 	ret = SSH_ERR_LIBCRYPTO_ERROR;
-         goto err;
-     }
- 
--    if (privkey_size != crypto_kem_mlkem768_SECRETKEYBYTES
--	    || pubkey_size != crypto_kem_mlkem768_PUBLICKEYBYTES) {
-+    if (privkey_size != got_priv_size || pubkey_size != got_pub_size) {
- 	ret = SSH_ERR_LIBCRYPTO_ERROR;
-         goto err;
-     }
-+
-+    if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &got_pub_size) <= 0
-+	|| EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &got_priv_size) <= 0) {
-+	ret = SSH_ERR_LIBCRYPTO_ERROR;
-+        goto err;
-+    }
-+
-     ret = 0;
- 
-  err:
-@@ -90,27 +110,57 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
- }
- 
- static int
--mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
-+mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
-+{
-+	return mlkem_keypair_gen("mlkem768", pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES,
-+			privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES);
-+}
-+
-+static int
-+mlkem1024_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf)
-+{
-+	return mlkem_keypair_gen("mlkem1024", pubkeybuf, crypto_kem_mlkem1024_PUBLICKEYBYTES,
-+			privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES);
-+}
-+
-+static int
-+mlkem_encap_secret(const char *mlkem_alg, const u_char *pubkeybuf, u_char *secret, u_char *out)
- {
-     EVP_PKEY *pkey = NULL;
-     EVP_PKEY_CTX *ctx = NULL;
-     int r = SSH_ERR_INTERNAL_ERROR;
--    size_t outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES,
--	   secretlen = crypto_kem_mlkem768_BYTES;
-+    size_t outlen, expected_outlen, publen, secretlen = crypto_kem_mlkem768_BYTES;
-+    int fips_fallback = 0;
-+
-+    if (strcmp(mlkem_alg, "mlkem768") == 0) {
-+	    outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES;
-+	    publen = crypto_kem_mlkem768_PUBLICKEYBYTES;
-+    } else if (strcmp(mlkem_alg, "mlkem1024") == 0) {
-+	    outlen = crypto_kem_mlkem1024_CIPHERTEXTBYTES;
-+	    publen = crypto_kem_mlkem1024_PUBLICKEYBYTES;
-+    } else
-+	    return r;
- 
--    pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "mlkem768", NULL,
--		    pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES);
-+    expected_outlen = outlen;
-+
-+    pkey = EVP_PKEY_new_raw_public_key_ex(NULL, mlkem_alg, NULL,
-+		    pubkeybuf, publen);
-+    if (pkey == NULL && FIPS_mode()) {
-+        pkey = EVP_PKEY_new_raw_public_key_ex(NULL, mlkem_alg, FIPS_FALLBACK_PROPQ,
-+		    pubkeybuf, publen);
-+	fips_fallback = 1;
-+    }
-     if (pkey == NULL) {
- 	r = SSH_ERR_LIBCRYPTO_ERROR;
- 	goto err;
-     }
- 
--    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
-+    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, fips_fallback ? FIPS_FALLBACK_PROPQ : NULL);
-     if (ctx == NULL
- 	|| EVP_PKEY_encapsulate_init(ctx, NULL) <= 0
--        || EVP_PKEY_encapsulate(ctx, out, &outlen, secret, &secretlen) <= 0
-+        || EVP_PKEY_encapsulate(ctx, out, &expected_outlen, secret, &secretlen) <= 0
- 	|| secretlen != crypto_kem_mlkem768_BYTES
--	|| outlen != crypto_kem_mlkem768_CIPHERTEXTBYTES) {
-+	|| outlen != expected_outlen) {
- 	r = SSH_ERR_LIBCRYPTO_ERROR;
- 	goto err;
-     }
-@@ -126,25 +176,45 @@ mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
- }
- 
- static int
--mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
-+mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
-+{
-+	return mlkem_encap_secret("mlkem768", pubkeybuf, secret, out);
-+}
-+
-+static int
-+mlkem1024_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out)
-+{
-+	return mlkem_encap_secret("mlkem1024", pubkeybuf, secret, out);
-+}
-+
-+static int
-+mlkem_decap_secret(const char *algname,
-+    const u_char *privkeybuf, size_t privkey_len,
-+    const u_char *wrapped, size_t wrapped_len,
-+    u_char *secret)
- {
-     EVP_PKEY *pkey = NULL;
-     EVP_PKEY_CTX *ctx = NULL;
-     int r = SSH_ERR_INTERNAL_ERROR;
--    size_t wrappedlen = crypto_kem_mlkem768_CIPHERTEXTBYTES,
--	   secretlen = crypto_kem_mlkem768_BYTES;
-+    size_t secretlen = crypto_kem_mlkem768_BYTES;
-+    int fips_fallback = 0;
- 
--    pkey = EVP_PKEY_new_raw_private_key_ex(NULL, "mlkem768", NULL,
--		    privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES);
-+    pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algname,
-+		    NULL, privkeybuf, privkey_len);
-+    if (pkey == NULL && FIPS_mode()) {
-+        pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algname,
-+		    FIPS_FALLBACK_PROPQ, privkeybuf, privkey_len);
-+	fips_fallback = 1;
-+    }
-     if (pkey == NULL) {
- 	r = SSH_ERR_LIBCRYPTO_ERROR;
- 	goto err;
-     }
- 
--    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
-+    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, fips_fallback ? FIPS_FALLBACK_PROPQ : NULL);
-     if (ctx == NULL
- 	|| EVP_PKEY_decapsulate_init(ctx, NULL) <= 0
--        || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrappedlen) <= 0
-+        || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrapped_len) <= 0
- 	|| secretlen != crypto_kem_mlkem768_BYTES) {
- 	r = SSH_ERR_LIBCRYPTO_ERROR;
- 	goto err;
-@@ -161,6 +231,20 @@ mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *s
-     return r;
- }
- 
-+static int
-+mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
-+{
-+	return mlkem_decap_secret("mlkem768", privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES,
-+		wrapped, crypto_kem_mlkem768_CIPHERTEXTBYTES, secret);
-+}
-+
-+static int
-+mlkem1024_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret)
-+{
-+	return mlkem_decap_secret("mlkem1024", privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES,
-+		wrapped, crypto_kem_mlkem1024_CIPHERTEXTBYTES, secret);
-+}
-+
- int
- kex_kem_mlkem768x25519_keypair(struct kex *kex)
- {
-@@ -337,7 +421,7 @@ kex_kem_mlkem768x25519_enc(struct kex *kex,
- 	u_char hash[SSH_DIGEST_MAX_LENGTH];
- 	size_t need;
- 	int r = SSH_ERR_INTERNAL_ERROR;
--	struct libcrux_mlkem768_enc_result enc; /* FIXME */
-+	struct libcrux_mlkem768_enc_result enc;
- 
- 	*server_blobp = NULL;
- 	*shared_secretp = NULL;
-@@ -546,6 +630,520 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
- 	return r;
- #endif
- }
-+
-+#define NIST_P256_COMPRESSED_LEN   33
-+#define NIST_P256_UNCOMPRESSED_LEN 65
-+#define NIST_P384_COMPRESSED_LEN   49
-+#define NIST_P384_UNCOMPRESSED_LEN 97
-+#define NIST_BUF_MAX_SIZE NIST_P384_UNCOMPRESSED_LEN
-+
-+static const char ec256[] = "P-256";
-+static const char ec384[] = "P-384";
-+static const char *len2curve_name(size_t len)
-+{
-+	switch (len) {
-+		case NIST_P256_COMPRESSED_LEN:
-+		case NIST_P256_UNCOMPRESSED_LEN:
-+			return ec256;
-+			break;
-+		case NIST_P384_COMPRESSED_LEN:
-+		case NIST_P384_UNCOMPRESSED_LEN:
-+			return ec384;
-+			break;
-+	}
-+	return NULL;
-+}
-+
-+static EVP_PKEY *
-+buf2nist_key(const unsigned char *pub_key_buf, size_t pub_key_len)
-+{
-+	EVP_PKEY *pkey = NULL;
-+	EVP_PKEY_CTX *ctx = NULL;
-+	OSSL_PARAM params[3];
-+	const char *curve_name = len2curve_name(pub_key_len);
-+
-+	if (curve_name == NULL)
-+		return NULL;
-+
-+	ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
-+	if (!ctx)
-+		goto err;
-+
-+	if (EVP_PKEY_fromdata_init(ctx) <= 0)
-+		goto err;
-+
-+	params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
-+	params[1] = OSSL_PARAM_construct_octet_string(
-+			OSSL_PKEY_PARAM_PUB_KEY, (void *)pub_key_buf, pub_key_len);
-+	params[2] = OSSL_PARAM_construct_end();
-+
-+	if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0)
-+		goto err;
-+
-+	EVP_PKEY_CTX_free(ctx);
-+	return pkey;
-+
-+err:
-+	EVP_PKEY_CTX_free(ctx);
-+	EVP_PKEY_free(pkey);
-+	return NULL;
-+}
-+
-+static int
-+kex_nist_shared_key_ext(EVP_PKEY *priv_key,
-+    const u_char *pub_key_buf, size_t pub_key_len, struct sshbuf *out)
-+{
-+	EVP_PKEY_CTX *ctx = NULL;
-+	unsigned char *shared_secret = NULL;
-+	size_t shared_secret_len = 0;
-+	EVP_PKEY *peer_key = buf2nist_key(pub_key_buf, pub_key_len);
-+	int r = SSH_ERR_INTERNAL_ERROR;
-+
-+	if (peer_key == NULL)
-+		return SSH_ERR_KEY_LENGTH;
-+
-+	ctx = EVP_PKEY_CTX_new_from_pkey(NULL, priv_key, NULL);
-+	if (!ctx)
-+		goto end;
-+
-+	if ((EVP_PKEY_derive_init(ctx) <= 0)
-+		|| EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0
-+		|| EVP_PKEY_derive(ctx, NULL, &shared_secret_len) <= 0)
-+		goto end;
-+
-+	shared_secret = OPENSSL_malloc(shared_secret_len);
-+	if (shared_secret == NULL)
-+		goto end;
-+
-+	if (EVP_PKEY_derive(ctx, shared_secret, &shared_secret_len) <= 0)
-+		goto end;
-+
-+	if ((r = sshbuf_put(out, shared_secret, shared_secret_len)) != 0)
-+		goto end;
-+
-+	r = 0;
-+
-+end:
-+	EVP_PKEY_free(peer_key);
-+	if (shared_secret)
-+		OPENSSL_clear_free(shared_secret, shared_secret_len);
-+	EVP_PKEY_CTX_free(ctx);
-+
-+	return r;
-+}
-+
-+static EVP_PKEY *
-+nist_pkey_keygen(size_t pub_key_len)
-+{
-+	const char *curve_name = len2curve_name(pub_key_len);
-+	EVP_PKEY_CTX *pctx = NULL;
-+	EVP_PKEY *pkey = NULL;
-+	OSSL_PARAM params[2];
-+
-+	if (curve_name == NULL)
-+		return NULL;
-+
-+	pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
-+	if (!pctx)
-+		return NULL;
-+
-+	if (EVP_PKEY_keygen_init(pctx) <= 0) {
-+		EVP_PKEY_CTX_free(pctx);
-+		return NULL;
-+	}
-+
-+	params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
-+	params[1] = OSSL_PARAM_construct_end();
-+
-+	if (EVP_PKEY_CTX_set_params(pctx, params) <= 0
-+	    || EVP_PKEY_keygen(pctx, &pkey) <= 0) {
-+		EVP_PKEY_CTX_free(pctx);
-+		return NULL;
-+	}
-+
-+	EVP_PKEY_CTX_free(pctx);
-+	return pkey;
-+}
-+
-+static size_t decompress_pub_key(void *pub, size_t compressed_len, size_t decompressed_len)
-+{
-+    EC_GROUP *group = NULL;
-+    EC_POINT *point = NULL;
-+    BN_CTX *ctx = NULL;
-+    size_t len = 0;
-+    int group_nid = NID_undef;
-+
-+    switch (compressed_len) {
-+    case NIST_P256_COMPRESSED_LEN:
-+         group_nid = NID_X9_62_prime256v1;
-+       break;
-+    case NIST_P384_COMPRESSED_LEN:
-+         group_nid = NID_secp384r1;
-+       break;
-+    default:
-+       return 0;
-+       break;
-+    }
-+
-+    ctx = BN_CTX_new();
-+    group = EC_GROUP_new_by_curve_name(group_nid);
-+    if (ctx == NULL || group == NULL)
-+        goto err;
-+
-+    point = EC_POINT_new(group);
-+    if (point == NULL)
-+        goto err;
-+
-+    if (!EC_POINT_oct2point(group, point, pub, compressed_len, ctx))
-+        goto err;
-+
-+    len = EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, pub, decompressed_len, ctx);
-+
-+err:
-+    EC_POINT_free(point);
-+    EC_GROUP_free(group);
-+    BN_CTX_free(ctx);
-+
-+    return len;
-+}
-+
-+static int
-+get_uncompressed_ec_pubkey(EVP_PKEY *pkey, unsigned char *buf, size_t buf_len)
-+{
-+    OSSL_PARAM params[2];
-+    size_t required_len = 0, out_len = 0;
-+
-+    params[0] = OSSL_PARAM_construct_utf8_string(
-+        OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT,
-+        "uncompressed", 0);
-+    params[1] = OSSL_PARAM_construct_end();
-+
-+    if (EVP_PKEY_set_params(pkey, params) <= 0
-+	    || EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
-+                                          buf, buf_len, &required_len) <= 0) {
-+        return SSH_ERR_LIBCRYPTO_ERROR;
-+    }
-+
-+    if (required_len != buf_len) {
-+        /* Red Hat certified FIPS provider ignores OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT
-+	 * We may have to perform the conversion manually */
-+        if (len2curve_name(required_len) == len2curve_name(buf_len)) {
-+	    out_len = decompress_pub_key(buf, required_len, buf_len);
-+	    if (out_len != buf_len) {
-+	        debug_f("Error decompressing the compressed public key");
-+	        return SSH_ERR_LIBCRYPTO_ERROR;
-+	    } else {
-+		return 0;
-+	    }
-+	} else {
-+	    debug_f("Unexpected length of uncompressed public key: expected %d, got %d", buf_len, required_len);
-+	    return SSH_ERR_LIBCRYPTO_ERROR;
-+	}
-+    }
-+
-+    return 0;
-+}
-+/* nist_bytes_len should always be uncompressed */
-+static int
-+kex_kem_mlkem_nist_keypair(struct kex *kex, size_t mlkem_bytes_len, size_t nist_bytes_len)
-+{
-+	struct sshbuf *buf = NULL;
-+	u_char *cp = NULL;
-+	size_t need;
-+	int r = SSH_ERR_INTERNAL_ERROR;
-+	u_char *client_key = NULL;
-+
-+	if ((buf = sshbuf_new()) == NULL)
-+		return SSH_ERR_ALLOC_FAIL;
-+	need = mlkem_bytes_len + nist_bytes_len;
-+	if ((r = sshbuf_reserve(buf, need, &cp)) != 0)
-+		goto out;
-+
-+	if (mlkem_bytes_len == crypto_kem_mlkem768_PUBLICKEYBYTES) {
-+		client_key = kex->mlkem768_client_key;
-+		r = mlkem768_keypair_gen(cp, client_key);
-+	}
-+
-+	if (mlkem_bytes_len == crypto_kem_mlkem1024_PUBLICKEYBYTES) {
-+		client_key = kex->mlkem1024_client_key;
-+		r = mlkem1024_keypair_gen(cp, client_key);
-+	}
-+
-+	if (client_key == NULL)
-+		goto out;
-+
-+	if (r != 0)
-+		goto out;
-+#ifdef DEBUG_KEXECDH
-+	dump_digest("client public key mlkemXXX:", cp, mlkem_bytes_len);
-+#endif
-+	cp += mlkem_bytes_len;
-+	if ((kex->ec_hybrid_client_key = nist_pkey_keygen(nist_bytes_len)) == NULL)
-+		goto out;
-+
-+	if ((r = get_uncompressed_ec_pubkey(kex->ec_hybrid_client_key, cp, nist_bytes_len)) != 0)
-+		goto out;
-+
-+#ifdef DEBUG_KEXECDH
-+	dump_digest("client public key NIST:", cp, nist_bytes_len);
-+#endif
-+	/* success */
-+	r = 0;
-+	kex->client_pub = buf;
-+	buf = NULL;
-+ out:
-+	sshbuf_free(buf);
-+        if (r == SSH_ERR_LIBCRYPTO_ERROR)
-+	   ERR_print_errors_fp(stderr);
-+
-+	return r;
-+}
-+
-+static int
-+kex_kem_mlkem_nist_enc(struct kex *kex, const char *nist_curve,
-+   const struct sshbuf *client_blob, struct sshbuf **server_blobp,
-+   struct sshbuf **shared_secretp)
-+{
-+	struct sshbuf *server_blob = NULL;
-+	struct sshbuf *buf = NULL;
-+	const u_char *client_pub;
-+	u_char server_pub[NIST_BUF_MAX_SIZE];
-+	u_char enc_out[crypto_kem_mlkem1024_CIPHERTEXTBYTES];
-+	u_char secret[crypto_kem_mlkem768_BYTES];
-+	EVP_PKEY *server_key = NULL;
-+	u_char hash[SSH_DIGEST_MAX_LENGTH];
-+	size_t client_buf_len, mlkem_buf_len, ecdh_buf_len, server_key_len, enc_out_len;
-+	int r = SSH_ERR_INTERNAL_ERROR;
-+
-+	*server_blobp = NULL;
-+	*shared_secretp = NULL;
-+
-+	client_buf_len = sshbuf_len(client_blob);
-+	/* client_blob contains both KEM and ECDH client pubkeys */
-+	if (strcmp(nist_curve, "P-256") == 0) {
-+		if (crypto_kem_mlkem768_PUBLICKEYBYTES > client_buf_len)
-+			return r;
-+
-+		ecdh_buf_len = client_buf_len - crypto_kem_mlkem768_PUBLICKEYBYTES;
-+		if (ecdh_buf_len != NIST_P256_COMPRESSED_LEN &&
-+			ecdh_buf_len != NIST_P256_UNCOMPRESSED_LEN)
-+			return r;
-+		mlkem_buf_len = crypto_kem_mlkem768_PUBLICKEYBYTES;
-+		enc_out_len = crypto_kem_mlkem768_CIPHERTEXTBYTES;
-+		server_key_len = NIST_P256_UNCOMPRESSED_LEN;
-+	} else if (strcmp(nist_curve, "P-384") == 0) {
-+		if (crypto_kem_mlkem1024_PUBLICKEYBYTES > client_buf_len)
-+			return r;
-+
-+		ecdh_buf_len = client_buf_len - crypto_kem_mlkem1024_PUBLICKEYBYTES;
-+		if (ecdh_buf_len != NIST_P384_COMPRESSED_LEN &&
-+			ecdh_buf_len != NIST_P384_UNCOMPRESSED_LEN)
-+			return r;
-+		mlkem_buf_len = crypto_kem_mlkem1024_PUBLICKEYBYTES;
-+		enc_out_len = crypto_kem_mlkem1024_CIPHERTEXTBYTES;
-+		server_key_len = NIST_P384_UNCOMPRESSED_LEN;
-+	} else
-+		return r;
-+
-+	client_pub = sshbuf_ptr(client_blob);
-+#ifdef DEBUG_KEXECDH
-+	dump_digest("client public key mlkem:", client_pub, mlkem_buf_len);
-+	dump_digest("client public key NIST:", client_pub + mlkem_buf_len, ecdh_buf_len);
-+#endif
-+
-+	/* allocate buffer for concatenation of KEM key and ECDH shared key */
-+	/* the buffer will be hashed and the result is the shared secret */
-+	if ((buf = sshbuf_new()) == NULL) {
-+		r = SSH_ERR_ALLOC_FAIL;
-+		goto out;
-+	}
-+	/* allocate space for encrypted KEM key and ECDH pub key */
-+	if ((server_blob = sshbuf_new()) == NULL) {
-+		r = SSH_ERR_ALLOC_FAIL;
-+		goto out;
-+	}
-+	r = (mlkem_buf_len == crypto_kem_mlkem768_PUBLICKEYBYTES) ?
-+		mlkem768_encap_secret(client_pub, secret, enc_out) :
-+		mlkem1024_encap_secret(client_pub, secret, enc_out);
-+
-+	if (r != 0)
-+		goto out;
-+
-+	/* generate ECDH key pair, store server pubkey after ciphertext */
-+	server_key = nist_pkey_keygen(server_key_len);
-+
-+	if ((server_key == NULL) ||
-+	    (r = get_uncompressed_ec_pubkey(server_key, server_pub, server_key_len) != 0) ||
-+	    (r = sshbuf_put(buf, secret, sizeof(secret))) != 0 ||
-+	    (r = sshbuf_put(server_blob, enc_out, enc_out_len) != 0)||
-+	    (r = sshbuf_put(server_blob, server_pub, server_key_len)) != 0)
-+		goto out;
-+
-+	/* append ECDH shared key */
-+	client_pub += mlkem_buf_len;
-+	if ((r = kex_nist_shared_key_ext(server_key, client_pub, ecdh_buf_len, buf)) < 0)
-+		goto out;
-+	if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0)
-+		goto out;
-+#ifdef DEBUG_KEXECDH
-+	dump_digest("server public NIST:", server_pub, server_key_len);
-+	dump_digest("server cipher text:", enc_out, enc_out_len);
-+	dump_digest("server kem key:", secret, sizeof(secret));
-+	dump_digest("concatenation of KEM key and ECDH shared key:",
-+	    sshbuf_ptr(buf), sshbuf_len(buf));
-+#endif
-+	/* string-encoded hash is resulting shared secret */
-+	sshbuf_reset(buf);
-+	if ((r = sshbuf_put_string(buf, hash,
-+	    ssh_digest_bytes(kex->hash_alg))) != 0)
-+		goto out;
-+#ifdef DEBUG_KEXECDH
-+	dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
-+#endif
-+	/* success */
-+	r = 0;
-+	*server_blobp = server_blob;
-+	*shared_secretp = buf;
-+	server_blob = NULL;
-+	buf = NULL;
-+ out:
-+	explicit_bzero(hash, sizeof(hash));
-+	EVP_PKEY_free(server_key);
-+	explicit_bzero(enc_out, sizeof(enc_out));
-+	explicit_bzero(secret, sizeof(secret));
-+	sshbuf_free(server_blob);
-+	sshbuf_free(buf);
-+	return r;
-+}
-+
-+static int
-+kex_kem_mlkem_nist_dec(struct kex *kex,
-+    const struct sshbuf *server_blob, struct sshbuf **shared_secretp,
-+    size_t mlkem_len)
-+{
-+	struct sshbuf *buf = NULL;
-+	const u_char *ciphertext, *server_pub;
-+	u_char hash[SSH_DIGEST_MAX_LENGTH];
-+	u_char decap[crypto_kem_mlkem768_BYTES];
-+	int r;
-+	size_t nist_len;
-+
-+	*shared_secretp = NULL;
-+
-+	if (sshbuf_len(server_blob) < mlkem_len) {
-+		r = SSH_ERR_SIGNATURE_INVALID;
-+		goto out;
-+	}
-+
-+	nist_len = sshbuf_len(server_blob) - mlkem_len;
-+
-+	switch (mlkem_len) {
-+		case crypto_kem_mlkem768_CIPHERTEXTBYTES:
-+			if (nist_len != NIST_P256_COMPRESSED_LEN
-+				&& nist_len != NIST_P256_UNCOMPRESSED_LEN) {
-+				r = SSH_ERR_SIGNATURE_INVALID;
-+				goto out;
-+			}
-+		break;
-+		case crypto_kem_mlkem1024_CIPHERTEXTBYTES:
-+			if (nist_len != NIST_P384_COMPRESSED_LEN
-+				&& nist_len != NIST_P384_UNCOMPRESSED_LEN) {
-+				r = SSH_ERR_SIGNATURE_INVALID;
-+				goto out;
-+			}
-+		break;
-+	}
-+
-+	ciphertext = sshbuf_ptr(server_blob);
-+	server_pub = ciphertext + mlkem_len;
-+	/* hash concatenation of KEM key and ECDH shared key */
-+	if ((buf = sshbuf_new()) == NULL) {
-+		r = SSH_ERR_ALLOC_FAIL;
-+		goto out;
-+	}
-+#ifdef DEBUG_KEXECDH
-+	dump_digest("server cipher text:", ciphertext, mlkem_len);
-+	dump_digest("server public key NIST:", server_pub, nist_len);
-+#endif
-+	r = (mlkem_len == crypto_kem_mlkem768_CIPHERTEXTBYTES) ?
-+		mlkem768_decap_secret(kex->mlkem768_client_key, ciphertext, decap) :
-+		mlkem1024_decap_secret(kex->mlkem1024_client_key, ciphertext, decap);
-+
-+	if (r != 0)
-+		goto out;
-+	if ((r = sshbuf_put(buf, decap, sizeof(decap))) != 0)
-+		goto out;
-+	if ((r = kex_nist_shared_key_ext(kex->ec_hybrid_client_key, server_pub,
-+		nist_len, buf)) < 0)
-+		goto out;
-+	if ((r = ssh_digest_buffer(kex->hash_alg, buf,
-+	    hash, sizeof(hash))) != 0)
-+		goto out;
-+#ifdef DEBUG_KEXECDH
-+	dump_digest("client kem key:", decap, sizeof(decap));
-+	dump_digest("concatenation of KEM key and ECDH shared key:",
-+	    sshbuf_ptr(buf), sshbuf_len(buf));
-+#endif
-+	sshbuf_reset(buf);
-+	if ((r = sshbuf_put_string(buf, hash,
-+	    ssh_digest_bytes(kex->hash_alg))) != 0)
-+		goto out;
-+#ifdef DEBUG_KEXECDH
-+	dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
-+#endif
-+	/* success */
-+	r = 0;
-+	*shared_secretp = buf;
-+	buf = NULL;
-+ out:
-+	explicit_bzero(hash, sizeof(hash));
-+	explicit_bzero(decap, sizeof(decap));
-+	sshbuf_free(buf);
-+	return r;
-+}
-+
-+int
-+kex_kem_mlkem768nistp256_keypair(struct kex *kex)
-+{
-+	return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem768_PUBLICKEYBYTES, NIST_P256_UNCOMPRESSED_LEN);
-+}
-+
-+int
-+kex_kem_mlkem768nistp256_enc(struct kex *kex, const struct sshbuf *client_blob,
-+    struct sshbuf **server_blobp, struct sshbuf **shared_secretp)
-+{
-+	return kex_kem_mlkem_nist_enc(kex, "P-256", client_blob, server_blobp, shared_secretp);
-+}
-+
-+int
-+kex_kem_mlkem768nistp256_dec(struct kex *kex, const struct sshbuf *server_blob,
-+    struct sshbuf **shared_secretp)
-+{
-+	return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp,
-+		crypto_kem_mlkem768_CIPHERTEXTBYTES);
-+}
-+
-+int
-+kex_kem_mlkem1024nistp384_keypair(struct kex *kex)
-+{
-+	return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem1024_PUBLICKEYBYTES, NIST_P384_UNCOMPRESSED_LEN);
-+}
-+
-+int
-+kex_kem_mlkem1024nistp384_enc(struct kex *kex, const struct sshbuf *client_blob,
-+    struct sshbuf **server_blobp, struct sshbuf **shared_secretp)
-+{
-+	return kex_kem_mlkem_nist_enc(kex, "P-384", client_blob, server_blobp, shared_secretp);
-+}
-+
-+int
-+kex_kem_mlkem1024nistp384_dec(struct kex *kex, const struct sshbuf *server_blob,
-+    struct sshbuf **shared_secretp)
-+{
-+	return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp,
-+		crypto_kem_mlkem1024_CIPHERTEXTBYTES);
-+}
-+
- #else /* USE_MLKEM768X25519 */
- int
- kex_kem_mlkem768x25519_keypair(struct kex *kex)
-@@ -567,4 +1165,39 @@ kex_kem_mlkem768x25519_dec(struct kex *kex,
- {
- 	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
- }
-+
-+int	 kex_kem_mlkem768nistp256_keypair(struct kex *)
-+{
-+	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int	 kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *,
-+    struct sshbuf **, struct sshbuf **)
-+{
-+	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int	 kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *,
-+    struct sshbuf **)
-+{
-+	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int	 kex_kem_mlkem1024nistp384_keypair(struct kex *)
-+{
-+	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int	 kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *,
-+    struct sshbuf **, struct sshbuf **)
-+{
-+	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
-+int	 kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *,
-+    struct sshbuf **)
-+{
-+	return SSH_ERR_SIGN_ALG_UNSUPPORTED;
-+}
-+
- #endif /* USE_MLKEM768X25519 */
-diff --git a/monitor.c b/monitor.c
-index bf5a5105b..36db1afee 100644
---- a/monitor.c
-+++ b/monitor.c
-@@ -2050,6 +2050,8 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor)
- 	kex->kex[KEX_C25519_SHA256] = kex_gen_server;
- 	kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
- 	kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
-+	kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
-+	kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
- 	kex->load_host_public_key=&get_hostkey_public_by_type;
- 	kex->load_host_private_key=&get_hostkey_private_by_type;
- 	kex->host_key_index=&get_hostkey_index;
-diff --git a/myproposal.h b/myproposal.h
-index 6433e0820..51e3ef5c8 100644
---- a/myproposal.h
-+++ b/myproposal.h
-@@ -26,6 +26,8 @@
- 
- #define KEX_SERVER_KEX	\
- 	"mlkem768x25519-sha256," \
-+	"mlkem768nistp256-sha256," \
-+	"mlkem1024nistp384-sha384," \
- 	"sntrup761x25519-sha512," \
- 	"sntrup761x25519-sha512@openssh.com," \
- 	"curve25519-sha256," \
-@@ -99,6 +101,8 @@
- 	"aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se," \
- 	"aes128-gcm@openssh.com,aes256-gcm@openssh.com"
- #define KEX_DEFAULT_KEX_FIPS		\
-+	"mlkem768nistp256-sha256," \
-+	"mlkem1024nistp384-sha384," \
- 	"ecdh-sha2-nistp256," \
- 	"ecdh-sha2-nistp384," \
- 	"ecdh-sha2-nistp521," \
-diff --git a/regress/unittests/kex/test_kex.c b/regress/unittests/kex/test_kex.c
-index f4700deeb..99a8e3b46 100644
---- a/regress/unittests/kex/test_kex.c
-+++ b/regress/unittests/kex/test_kex.c
-@@ -171,6 +171,8 @@ do_kex_with_key(char *kex, char *cipher, char *mac,
- 	server2->kex->kex[KEX_C25519_SHA256] = kex_gen_server;
- 	server2->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
- 	server2->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
-+	server2->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
-+	server2->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
- 	server2->kex->load_host_public_key = server->kex->load_host_public_key;
- 	server2->kex->load_host_private_key = server->kex->load_host_private_key;
- 	server2->kex->sign = server->kex->sign;
-@@ -248,6 +250,8 @@ kex_tests(void)
- 	}
- # ifdef USE_MLKEM768X25519
- 	do_kex("mlkem768x25519-sha256");
-+	do_kex("mlkem768nistp256-sha256");
-+	do_kex("mlkem1024nistp384-sha384");
- # endif /* USE_MLKEM768X25519 */
- # ifdef USE_SNTRUP761X25519
- 	do_kex("sntrup761x25519-sha512");
-diff --git a/ssh-keyscan.c b/ssh-keyscan.c
-index 3cea1daac..f5ce90434 100644
---- a/ssh-keyscan.c
-+++ b/ssh-keyscan.c
-@@ -300,6 +300,8 @@ keygrab_ssh2(con *c)
- 	c->c_ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
- 	c->c_ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
- 	c->c_ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
-+	c->c_ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
-+	c->c_ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
- 	ssh_set_verify_host_key_callback(c->c_ssh, key_print_wrapper);
- 	/*
- 	 * do the key-exchange until an error occurs or until
-diff --git a/ssh_api.c b/ssh_api.c
-index 38ac17da1..b178582b3 100644
---- a/ssh_api.c
-+++ b/ssh_api.c
-@@ -135,6 +135,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params)
- 		ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_server;
- 		ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
- 		ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
-+		ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
-+		ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
- 		ssh->kex->load_host_public_key=&_ssh_host_public_key;
- 		ssh->kex->load_host_private_key=&_ssh_host_private_key;
- 		ssh->kex->sign=&_ssh_host_key_sign;
-@@ -154,6 +156,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params)
- 		ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
- 		ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
- 		ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
-+		ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
-+		ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
- 		ssh->kex->verify_host_key =&_ssh_verify_host_key;
- 	}
- 	*sshp = ssh;
-diff --git a/sshconnect2.c b/sshconnect2.c
-index eee1f9794..50c228802 100644
---- a/sshconnect2.c
-+++ b/sshconnect2.c
-@@ -350,6 +350,8 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
- 	ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client;
- 	ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client;
- 	ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client;
-+	ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client;
-+	ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client;
- 	ssh->kex->verify_host_key=&verify_host_key_callback;
- 
- #if defined(GSSAPI) && defined(WITH_OPENSSL)
-diff --git a/sshd-auth.c b/sshd-auth.c
-index 885cd8281..a5d2a1a6e 100644
---- a/sshd-auth.c
-+++ b/sshd-auth.c
-@@ -893,6 +893,8 @@ do_ssh2_kex(struct ssh *ssh)
- 	kex->kex[KEX_C25519_SHA256] = kex_gen_server;
- 	kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server;
- 	kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server;
-+	kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server;
-+	kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server;
- 	kex->load_host_public_key=&get_hostkey_public_by_type;
- 	kex->load_host_private_key=&get_hostkey_private_by_type;
- 	kex->host_key_index=&get_hostkey_index;
--- 
-2.53.0
-

diff --git a/0048-openssh-7.3p1-x11-max-displays.patch b/0048-openssh-7.3p1-x11-max-displays.patch
new file mode 100644
index 0000000..394a226
--- /dev/null
+++ b/0048-openssh-7.3p1-x11-max-displays.patch
@@ -0,0 +1,150 @@
+From 764fa7ebd0f6d5f77ca501d0bd1feec71f811911 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Fri, 12 Dec 2025 15:35:14 +0100
+Subject: [PATCH 48/54] openssh-7.3p1-x11-max-displays
+
+Move MAX_DISPLAYS to a configuration option (#1341302)
+---
+ channels.c    | 9 ++++++---
+ channels.h    | 2 +-
+ servconf.c    | 5 +++++
+ servconf.h    | 2 ++
+ session.c     | 5 +++--
+ sshd_config.5 | 7 +++++++
+ 6 files changed, 24 insertions(+), 6 deletions(-)
+
+diff --git a/channels.c b/channels.c
+index bae84e5f9..48ec0ab83 100644
+--- a/channels.c
++++ b/channels.c
+@@ -5085,7 +5085,7 @@ rdynamic_connect_finish(struct ssh *ssh, Channel *c)
+  */
+ int
+ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
+-    int x11_use_localhost, int single_connection,
++    int x11_use_localhost, int x11_max_displays, int single_connection,
+     u_int *display_numberp, int **chanids)
+ {
+ 	Channel *nc = NULL;
+@@ -5098,8 +5098,11 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
+ 	    x11_display_offset > UINT16_MAX - X11_BASE_PORT - MAX_DISPLAYS)
+ 		return -1;
+ 
++	/* Try to bind ports starting at 6000+X11DisplayOffset */
++	x11_max_displays = x11_max_displays + x11_display_offset;
++
+ 	for (display_number = x11_display_offset;
+-	    display_number < x11_display_offset + MAX_DISPLAYS;
++	    display_number < x11_max_displays;
+ 	    display_number++) {
+ 		port = X11_BASE_PORT + display_number;
+ 		memset(&hints, 0, sizeof(hints));
+@@ -5164,7 +5167,7 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
+ 		if (num_socks > 0)
+ 			break;
+ 	}
+-	if (display_number >= x11_display_offset + MAX_DISPLAYS) {
++	if (display_number >= x11_max_displays || port < X11_BASE_PORT ) {
+ 		error("Failed to allocate internet-domain X11 display socket.");
+ 		return -1;
+ 	}
+diff --git a/channels.h b/channels.h
+index f69096498..bc3b52aa2 100644
+--- a/channels.h
++++ b/channels.h
+@@ -391,7 +391,7 @@ int	 permitopen_port(const char *);
+ 
+ void	 channel_set_x11_refuse_time(struct ssh *, time_t);
+ int	 x11_connect_display(struct ssh *);
+-int	 x11_create_display_inet(struct ssh *, int, int, int, u_int *, int **);
++int	 x11_create_display_inet(struct ssh *, int, int, int, int, u_int *, int **);
+ void	 x11_request_forwarding_with_spoofing(struct ssh *, int,
+ 	    const char *, const char *, const char *, int);
+ int      x11_channel_used_recently(struct ssh *ssh);
+diff --git a/servconf.c b/servconf.c
+index 90c5839a2..03b84ecb8 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -1535,6 +1535,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ 			*intptr = value;
+ 		break;
+ 
++	case sX11MaxDisplays:
++		intptr = &options->x11_max_displays;
++		goto parse_int;
++
+ 	case sX11UseLocalhost:
+ 		intptr = &options->x11_use_localhost;
+ 		goto parse_flag;
+@@ -4250,6 +4254,7 @@ dump_config(ServerOptions *o)
+ #endif
+ 	dump_cfg_int(sLoginGraceTime, o->login_grace_time);
+ 	dump_cfg_int(sX11DisplayOffset, o->x11_display_offset);
++	dump_cfg_int(sX11MaxDisplays, o->x11_max_displays);
+ 	dump_cfg_int(sMaxAuthTries, o->max_authtries);
+ 	dump_cfg_int(sMaxSessions, o->max_sessions);
+ 	dump_cfg_int(sClientAliveInterval, o->client_alive_interval);
+diff --git a/servconf.h b/servconf.h
+index a8e90e5a9..c1bbd82b8 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -40,6 +40,7 @@ struct sshbuf;
+ 
+ #define DEFAULT_AUTH_FAIL_MAX	6	/* Default for MaxAuthTries */
+ #define DEFAULT_SESSIONS_MAX	10	/* Default for MaxSessions */
++#define DEFAULT_MAX_DISPLAYS	1000 /* Maximum number of fake X11 displays to try. */
+ 
+ /* Magic name for internal sftp-server */
+ #define INTERNAL_SFTP_NAME	"internal-sftp"
+@@ -172,6 +173,7 @@ SSHCONF_INTFLAG(ignore_user_known_hosts, IgnoreUserKnownHosts, SSHCFG_GLOBAL, 0,
+ SSHCONF_INTFLAG(print_motd, PrintMotd, SSHCFG_GLOBAL, 1, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(x11_forwarding, X11Forwarding, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+ SSHCONF_INT(x11_display_offset, X11DisplayOffset, SSHCFG_ALL, NULL, SSHD_DEFAULT_X11_DISPLAY_OFFSET, SSHCFG_COPY_MATCH) \
++SSHCONF_INT(x11_max_displays, X11MaxDisplays, SSHCFG_ALL, NULL, DEFAULT_MAX_DISPLAYS, SSHCFG_COPY_MATCH) \
+ SSHCONF_INTFLAG(x11_use_localhost, X11UseLocalhost, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
+ SSHCONF_STRING(xauth_location, XAuthLocation, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(permit_tty, PermitTTY, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
+diff --git a/session.c b/session.c
+index bd8dc808c..e2cf13bdc 100644
+--- a/session.c
++++ b/session.c
+@@ -2693,8 +2693,9 @@ session_setup_x11fwd(struct ssh *ssh, Session *s)
+ 		return 0;
+ 	}
+ 	if (x11_create_display_inet(ssh, options.x11_display_offset,
+-	    options.x11_use_localhost, s->single_connection,
+-	    &s->display_number, &s->x11_chanids) == -1) {
++	    options.x11_use_localhost, options.x11_max_displays,
++	    s->single_connection, &s->display_number,
++	    &s->x11_chanids) == -1) {
+ 		debug("x11_create_display_inet failed.");
+ 		return 0;
+ 	}
+diff --git a/sshd_config.5 b/sshd_config.5
+index 8fed58d5c..8cbe5ecba 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -1427,6 +1427,7 @@ Available keywords are
+ .Cm TrustedUserCAKeys ,
+ .Cm UnusedConnectionTimeout ,
+ .Cm X11DisplayOffset ,
++.Cm X11MaxDisplays ,
+ .Cm X11Forwarding
+ and
+ .Cm X11UseLocalhost .
+@@ -2144,6 +2145,12 @@ Specifies the first display number available for
+ X11 forwarding.
+ This prevents sshd from interfering with real X11 servers.
+ The default is 10.
++.It Cm X11MaxDisplays
++Specifies the maximum number of displays available for
++.Xr sshd 8 Ns 's
++X11 forwarding.
++This prevents sshd from exhausting local ports.
++The default is 1000.
+ .It Cm X11Forwarding
+ Specifies whether X11 forwarding is permitted.
+ The argument must be
+-- 
+2.55.0
+

diff --git a/0049-Fix-ssh-pkcs11-client-helper-termination.patch b/0049-Fix-ssh-pkcs11-client-helper-termination.patch
new file mode 100644
index 0000000..fbbff16
--- /dev/null
+++ b/0049-Fix-ssh-pkcs11-client-helper-termination.patch
@@ -0,0 +1,48 @@
+From eedce865a314caca63838313c5de1f258bbfc7d0 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 13 Apr 2026 14:59:26 +0200
+Subject: [PATCH 49/54] Fix ssh-pkcs11-client helper termination
+
+Don't terminate the PKCS#11 helper when SSH2_AGENT_FAILURE is returned.
+This is a legitimate response when a token requires PIN authentication.
+The helper must remain active so it can prompt for PIN during actual key use.
+
+Only terminate the helper on unexpected responses or communication failures.
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ ssh-pkcs11-client.c | 18 ++++++++++++------
+ 1 file changed, 12 insertions(+), 6 deletions(-)
+
+diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c
+index 6d74d7280..30a4cf5dc 100644
+--- a/ssh-pkcs11-client.c
++++ b/ssh-pkcs11-client.c
+@@ -429,12 +429,18 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
+ 		}
+ 		/* success */
+ 		ret = 0;
+-	} else if (type == SSH2_AGENT_FAILURE) {
+-		if ((r = sshbuf_get_u32(msg, &nkeys)) != 0)
+-			error_fr(r, "failed to parse failure response");
+-	}
+-	if (ret != 0) {
+-		debug_f("no keys; terminate helper");
++	} else if (type == SSH_AGENT_FAILURE || type == SSH2_AGENT_FAILURE) {
++		if (type == SSH2_AGENT_FAILURE) {
++			if ((r = sshbuf_get_u32(msg, &nkeys)) != 0)
++				error_fr(r, "failed to parse failure response");
++		}
++		/* Provider registered but returned no keys (may need PIN) */
++		/* Don't terminate helper - keep it for later use */
++		ret = 0;
++		nkeys = 0;
++	} else {
++		/* Unexpected response - terminate helper */
++		debug_f("unexpected response %d; terminate helper", type);
+ 		helper_terminate(helper);
+ 	}
+ 	sshbuf_free(msg);
+-- 
+2.55.0
+

diff --git a/0049-openssh-7.3p1-x11-max-displays.patch b/0049-openssh-7.3p1-x11-max-displays.patch
deleted file mode 100644
index 3e9592c..0000000
--- a/0049-openssh-7.3p1-x11-max-displays.patch
+++ /dev/null
@@ -1,192 +0,0 @@
-From f8a244536a7925d63047245a34e8f062227ac56f Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Fri, 12 Dec 2025 15:35:14 +0100
-Subject: [PATCH 49/54] openssh-7.3p1-x11-max-displays
-
-Move MAX_DISPLAYS to a configuration option (#1341302)
----
- channels.c    |  9 ++++++---
- channels.h    |  2 +-
- servconf.c    | 12 +++++++++++-
- servconf.h    |  2 ++
- session.c     |  5 +++--
- sshd_config.5 |  7 +++++++
- 6 files changed, 30 insertions(+), 7 deletions(-)
-
-diff --git a/channels.c b/channels.c
-index 14d536d64..73eacc5f6 100644
---- a/channels.c
-+++ b/channels.c
-@@ -5076,7 +5076,7 @@ rdynamic_connect_finish(struct ssh *ssh, Channel *c)
-  */
- int
- x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
--    int x11_use_localhost, int single_connection,
-+    int x11_use_localhost, int x11_max_displays, int single_connection,
-     u_int *display_numberp, int **chanids)
- {
- 	Channel *nc = NULL;
-@@ -5089,8 +5089,11 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
- 	    x11_display_offset > UINT16_MAX - X11_BASE_PORT - MAX_DISPLAYS)
- 		return -1;
- 
-+	/* Try to bind ports starting at 6000+X11DisplayOffset */
-+	x11_max_displays = x11_max_displays + x11_display_offset;
-+
- 	for (display_number = x11_display_offset;
--	    display_number < x11_display_offset + MAX_DISPLAYS;
-+	    display_number < x11_max_displays;
- 	    display_number++) {
- 		port = X11_BASE_PORT + display_number;
- 		memset(&hints, 0, sizeof(hints));
-@@ -5155,7 +5158,7 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
- 		if (num_socks > 0)
- 			break;
- 	}
--	if (display_number >= x11_display_offset + MAX_DISPLAYS) {
-+	if (display_number >= x11_max_displays || port < X11_BASE_PORT ) {
- 		error("Failed to allocate internet-domain X11 display socket.");
- 		return -1;
- 	}
-diff --git a/channels.h b/channels.h
-index 2fcf9f8cb..7bf37b053 100644
---- a/channels.h
-+++ b/channels.h
-@@ -390,7 +390,7 @@ int	 permitopen_port(const char *);
- 
- void	 channel_set_x11_refuse_time(struct ssh *, time_t);
- int	 x11_connect_display(struct ssh *);
--int	 x11_create_display_inet(struct ssh *, int, int, int, u_int *, int **);
-+int	 x11_create_display_inet(struct ssh *, int, int, int, int, u_int *, int **);
- void	 x11_request_forwarding_with_spoofing(struct ssh *, int,
- 	    const char *, const char *, const char *, int);
- int      x11_channel_used_recently(struct ssh *ssh);
-diff --git a/servconf.c b/servconf.c
-index 268c09d1c..f4d6b19dd 100644
---- a/servconf.c
-+++ b/servconf.c
-@@ -118,6 +118,7 @@ initialize_server_options(ServerOptions *options)
- 	options->print_lastlog = -1;
- 	options->x11_forwarding = -1;
- 	options->x11_display_offset = -1;
-+	options->x11_max_displays = -1;
- 	options->x11_use_localhost = -1;
- 	options->permit_tty = -1;
- 	options->permit_user_rc = -1;
-@@ -359,6 +360,8 @@ fill_default_server_options(ServerOptions *options)
- 		options->x11_forwarding = 0;
- 	if (options->x11_display_offset == -1)
- 		options->x11_display_offset = 10;
-+	if (options->x11_max_displays == -1)
-+		options->x11_max_displays = DEFAULT_MAX_DISPLAYS;
- 	if (options->x11_use_localhost == -1)
- 		options->x11_use_localhost = 1;
- 	if (options->xauth_location == NULL)
-@@ -588,7 +591,7 @@ typedef enum {
- 	sKerberosGetAFSToken, sKerberosUniqueCCache, sKerberosUseKuserok, sPasswordAuthentication,
- 	sKbdInteractiveAuthentication, sListenAddress, sAddressFamily,
- 	sPrintMotd, sPrintLastLog, sIgnoreRhosts,
--	sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
-+	sX11Forwarding, sX11DisplayOffset, sX11MaxDisplays, sX11UseLocalhost,
- 	sPermitTTY, sStrictModes, sEmptyPasswd, sTCPKeepAlive,
- 	sPermitUserEnvironment, sAllowTcpForwarding, sCompression,
- 	sRekeyLimit, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
-@@ -730,6 +733,7 @@ static struct {
- 	{ "ignoreuserknownhosts", sIgnoreUserKnownHosts, SSHCFG_GLOBAL },
- 	{ "x11forwarding", sX11Forwarding, SSHCFG_ALL },
- 	{ "x11displayoffset", sX11DisplayOffset, SSHCFG_ALL },
-+	{ "x11maxdisplays", sX11MaxDisplays, SSHCFG_ALL },
- 	{ "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL },
- 	{ "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL },
- 	{ "strictmodes", sStrictModes, SSHCFG_GLOBAL },
-@@ -1781,6 +1785,10 @@ process_server_config_line_depth(ServerOptions *options, char *line,
- 			*intptr = value;
- 		break;
- 
-+	case sX11MaxDisplays:
-+		intptr = &options->x11_max_displays;
-+		goto parse_int;
-+
- 	case sX11UseLocalhost:
- 		intptr = &options->x11_use_localhost;
- 		goto parse_flag;
-@@ -3065,6 +3073,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
- 	M_CP_INTOPT(fwd_opts.streamlocal_bind_unlink);
- 	M_CP_INTOPT(x11_display_offset);
- 	M_CP_INTOPT(x11_forwarding);
-+	M_CP_INTOPT(x11_max_displays);
- 	M_CP_INTOPT(x11_use_localhost);
- 	M_CP_INTOPT(permit_tty);
- 	M_CP_INTOPT(permit_user_rc);
-@@ -3361,6 +3370,7 @@ dump_config(ServerOptions *o)
- #endif
- 	dump_cfg_int(sLoginGraceTime, o->login_grace_time);
- 	dump_cfg_int(sX11DisplayOffset, o->x11_display_offset);
-+	dump_cfg_int(sX11MaxDisplays, o->x11_max_displays);
- 	dump_cfg_int(sMaxAuthTries, o->max_authtries);
- 	dump_cfg_int(sMaxSessions, o->max_sessions);
- 	dump_cfg_int(sClientAliveInterval, o->client_alive_interval);
-diff --git a/servconf.h b/servconf.h
-index 781f20c61..fea25947e 100644
---- a/servconf.h
-+++ b/servconf.h
-@@ -38,6 +38,7 @@
- 
- #define DEFAULT_AUTH_FAIL_MAX	6	/* Default for MaxAuthTries */
- #define DEFAULT_SESSIONS_MAX	10	/* Default for MaxSessions */
-+#define DEFAULT_MAX_DISPLAYS	1000 /* Maximum number of fake X11 displays to try. */
- 
- /* Magic name for internal sftp-server */
- #define INTERNAL_SFTP_NAME	"internal-sftp"
-@@ -115,6 +116,7 @@ typedef struct {
- 	int     x11_forwarding;	/* If true, permit inet (spoofing) X11 fwd. */
- 	int     x11_display_offset;	/* What DISPLAY number to start
- 					 * searching at */
-+	int 	x11_max_displays; /* Number of displays to search */
- 	int     x11_use_localhost;	/* If true, use localhost for fake X11 server. */
- 	char   *xauth_location;	/* Location of xauth program */
- 	int	permit_tty;	/* If false, deny pty allocation */
-diff --git a/session.c b/session.c
-index 1a7f5ca7a..52f86fd46 100644
---- a/session.c
-+++ b/session.c
-@@ -2689,8 +2689,9 @@ session_setup_x11fwd(struct ssh *ssh, Session *s)
- 		return 0;
- 	}
- 	if (x11_create_display_inet(ssh, options.x11_display_offset,
--	    options.x11_use_localhost, s->single_connection,
--	    &s->display_number, &s->x11_chanids) == -1) {
-+	    options.x11_use_localhost, options.x11_max_displays,
-+	    s->single_connection, &s->display_number,
-+	    &s->x11_chanids) == -1) {
- 		debug("x11_create_display_inet failed.");
- 		return 0;
- 	}
-diff --git a/sshd_config.5 b/sshd_config.5
-index 4ca80016c..c28220077 100644
---- a/sshd_config.5
-+++ b/sshd_config.5
-@@ -1416,6 +1416,7 @@ Available keywords are
- .Cm TrustedUserCAKeys ,
- .Cm UnusedConnectionTimeout ,
- .Cm X11DisplayOffset ,
-+.Cm X11MaxDisplays ,
- .Cm X11Forwarding
- and
- .Cm X11UseLocalhost .
-@@ -2133,6 +2134,12 @@ Specifies the first display number available for
- X11 forwarding.
- This prevents sshd from interfering with real X11 servers.
- The default is 10.
-+.It Cm X11MaxDisplays
-+Specifies the maximum number of displays available for
-+.Xr sshd 8 Ns 's
-+X11 forwarding.
-+This prevents sshd from exhausting local ports.
-+The default is 1000.
- .It Cm X11Forwarding
- Specifies whether X11 forwarding is permitted.
- The argument must be
--- 
-2.53.0
-

diff --git a/0050-Fix-ssh-pkcs11-client-helper-termination.patch b/0050-Fix-ssh-pkcs11-client-helper-termination.patch
deleted file mode 100644
index 1b85620..0000000
--- a/0050-Fix-ssh-pkcs11-client-helper-termination.patch
+++ /dev/null
@@ -1,48 +0,0 @@
-From f9f68e3d5e1d4bf2e945a189c6688b27abf9810b Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 13 Apr 2026 14:59:26 +0200
-Subject: [PATCH 50/54] Fix ssh-pkcs11-client helper termination
-
-Don't terminate the PKCS#11 helper when SSH2_AGENT_FAILURE is returned.
-This is a legitimate response when a token requires PIN authentication.
-The helper must remain active so it can prompt for PIN during actual key use.
-
-Only terminate the helper on unexpected responses or communication failures.
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- ssh-pkcs11-client.c | 18 ++++++++++++------
- 1 file changed, 12 insertions(+), 6 deletions(-)
-
-diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c
-index 6d74d7280..30a4cf5dc 100644
---- a/ssh-pkcs11-client.c
-+++ b/ssh-pkcs11-client.c
-@@ -429,12 +429,18 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
- 		}
- 		/* success */
- 		ret = 0;
--	} else if (type == SSH2_AGENT_FAILURE) {
--		if ((r = sshbuf_get_u32(msg, &nkeys)) != 0)
--			error_fr(r, "failed to parse failure response");
--	}
--	if (ret != 0) {
--		debug_f("no keys; terminate helper");
-+	} else if (type == SSH_AGENT_FAILURE || type == SSH2_AGENT_FAILURE) {
-+		if (type == SSH2_AGENT_FAILURE) {
-+			if ((r = sshbuf_get_u32(msg, &nkeys)) != 0)
-+				error_fr(r, "failed to parse failure response");
-+		}
-+		/* Provider registered but returned no keys (may need PIN) */
-+		/* Don't terminate helper - keep it for later use */
-+		ret = 0;
-+		nkeys = 0;
-+	} else {
-+		/* Unexpected response - terminate helper */
-+		debug_f("unexpected response %d; terminate helper", type);
- 		helper_terminate(helper);
- 	}
- 	sshbuf_free(msg);
--- 
-2.53.0
-

diff --git a/0050-openssh-10.2p1-pam-auth.patch b/0050-openssh-10.2p1-pam-auth.patch
new file mode 100644
index 0000000..9f6956a
--- /dev/null
+++ b/0050-openssh-10.2p1-pam-auth.patch
@@ -0,0 +1,43 @@
+From e002da5afc245e3ed67f8319bd1c6ded83e959a4 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Sun, 12 Apr 2026 12:25:04 +0200
+Subject: [PATCH 50/54] openssh-10.2p1-pam-auth
+
+https://bugzilla.redhat.com/show_bug.cgi?id=2423900
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ auth-pam.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/auth-pam.c b/auth-pam.c
+index d3dcd037b..aaad16765 100644
+--- a/auth-pam.c
++++ b/auth-pam.c
+@@ -455,6 +455,7 @@ static int
+ check_pam_user(Authctxt *authctxt)
+ {
+ 	const char *pam_user;
++	const struct passwd *pam_pw;
+ 
+ 	if (authctxt == NULL || authctxt->pw == NULL ||
+ 	    authctxt->pw->pw_name == NULL)
+@@ -469,11 +470,10 @@ check_pam_user(Authctxt *authctxt)
+ 		return PAM_USER_UNKNOWN;
+ 	}
+ 
+-	if (sshpam_initial_user == NULL)
+-		fatal_f("internal error: sshpam_initial_user NULL");
+-	if (strcmp(sshpam_initial_user, pam_user) != 0) {
+-		error_f("PAM user \"%s\" does not match previous \"%s\"",
+-		      pam_user, sshpam_initial_user);
++	pam_pw = getpwnam(pam_user);
++	if (pam_pw == NULL || pam_pw->pw_uid != authctxt->pw->pw_uid) {
++		debug("PAM user \"%s\" does not match expected \"%s\"",
++		      pam_user, authctxt->pw->pw_name);
+ 		return PAM_USER_UNKNOWN;
+ 	}
+ 	return PAM_SUCCESS;
+-- 
+2.55.0
+

diff --git a/0051-gssapi-s4u.patch b/0051-gssapi-s4u.patch
new file mode 100644
index 0000000..416e609
--- /dev/null
+++ b/0051-gssapi-s4u.patch
@@ -0,0 +1,1027 @@
+From a952c4cfa579551fadae91dd224f43f768943167 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Sun, 12 Apr 2026 12:25:21 +0200
+Subject: [PATCH 51/54] gssapi-s4u
+
+Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
+---
+ auth-krb5.c     |  13 ++
+ configure.ac    |  10 +-
+ gss-serv-krb5.c | 225 ++++++++++++++++++++++++++++
+ gss-serv.c      | 388 ++++++++++++++++++++++++++++++++++++++++++++++--
+ servconf.c      |  46 ++++++
+ servconf.h      |   8 +-
+ ssh-gss.h       |  13 ++
+ sshd-session.c  |  98 ++++++++++++
+ sshd_config.5   |  70 +++++++++
+ 9 files changed, 851 insertions(+), 20 deletions(-)
+
+diff --git a/auth-krb5.c b/auth-krb5.c
+index 287bdd35a..7d4fbcc3d 100644
+--- a/auth-krb5.c
++++ b/auth-krb5.c
+@@ -463,6 +463,19 @@ ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environm
+ 	 * a primary cache for this collection, if it supports that (non-FILE)
+ 	 */
+ 	if (krb5_cc_support_switch(ctx, type)) {
++		/*
++		 * For collection-type caches (KCM, KEYRING, …) reuse the
++		 * existing primary ccache when one is already present.  The
++		 * caller will reinitialise it with krb5_cc_initialize(), so
++		 * its old contents are replaced rather than orphaned.  Only
++		 * create a fresh unique ccache when no primary exists yet.
++		 */
++		if (krb5_cc_default(ctx, ccache) == 0) {
++			debug3_f("reusing existing default ccache of type %s",
++			    type);
++			free(type);
++			return 0;
++		}
+ 		debug3_f("calling cc_new_unique(%s)", ccname);
+ 		ret = krb5_cc_new_unique(ctx, type, NULL, ccache);
+ 		free(type);
+diff --git a/configure.ac b/configure.ac
+index 4df1a3204..918ccd5df 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -5136,11 +5136,17 @@ AC_ARG_WITH([kerberos5],
+ # include <gssapi_generic.h>
+ #elif defined(HAVE_GSSAPI_GSSAPI_GENERIC_H)
+ # include <gssapi/gssapi_generic.h>
++#endif
++#ifdef HAVE_GSSAPI_EXT_H
++# include <gssapi/gssapi_ext.h>
++#endif
++#ifdef HAVE_GSSAPI_KRB5_H
++# include <gssapi/gssapi_krb5.h>
+ #endif
+ 		]])
+ 		saved_LIBS="$LIBS"
+-		LIBS="$LIBS $K5LIBS"
+-		AC_CHECK_FUNCS([krb5_cc_new_unique krb5_get_error_message krb5_free_error_message])
++		LIBS="$LIBS $GSSLIBS $K5LIBS "
++		AC_CHECK_FUNCS([krb5_cc_new_unique krb5_get_error_message krb5_free_error_message gss_acquire_cred_from])
+ 		LIBS="$saved_LIBS"
+ 
+ 	fi
+diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
+index fc7cabc09..06d450d4d 100644
+--- a/gss-serv-krb5.c
++++ b/gss-serv-krb5.c
+@@ -609,6 +609,231 @@ ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store,
+ 	return 1;
+ }
+ 
++/*
++ * Check whether the user's default ccache already contains valid service
++ * tickets for all principals listed in services[].  Returns 1 if every
++ * listed service has a ticket with at least min_lifetime seconds remaining
++ * (pass GSS_C_INDEFINITE to accept any positive remaining lifetime), 0 if
++ * any ticket is missing or too close to expiry.  Runs as the user.
++ *
++ * Unlike ssh_gssapi_user_has_valid_tgt(), this cannot use gss_acquire_cred()
++ * because service tickets are not initiator credentials — GSSAPI only
++ * surfaces TGTs via that API.  We iterate the ccache with the krb5 API
++ * directly instead.
++ */
++int
++ssh_gssapi_user_has_valid_proxy_tickets(char **services, u_int nservices,
++    u_int min_lifetime)
++{
++	krb5_context ctx = NULL;
++	krb5_ccache cc = NULL;
++	krb5_cc_cursor cursor;
++	krb5_creds cred;
++	krb5_principal svc_princ;
++	int *found = NULL;
++	u_int i;
++	int all_found = 0;
++	time_t now;
++
++	if (nservices == 0)
++		return 0;
++
++	if (krb5_init_context(&ctx) != 0)
++		return 0;
++
++	found = xcalloc(nservices, sizeof(*found));
++	now = time(NULL);
++
++	if (krb5_cc_default(ctx, &cc) != 0)
++		goto out;
++
++	if (krb5_cc_start_seq_get(ctx, cc, &cursor) != 0) {
++		krb5_cc_close(ctx, cc);
++		cc = NULL;
++		goto out;
++	}
++
++	while (krb5_cc_next_cred(ctx, cc, &cursor, &cred) == 0) {
++		/*
++		 * Use krb5_principal_compare() so that service names
++		 * configured without an explicit realm (krb5_parse_name
++		 * appends the default realm) still match the fully-
++		 * qualified principal stored in the ccache.
++		 */
++		for (i = 0; i < nservices; i++) {
++			if (found[i])
++				continue;
++			if (krb5_parse_name(ctx, services[i],
++			    &svc_princ) != 0)
++				continue;
++			if (krb5_principal_compare(ctx,
++			    cred.server, svc_princ)) {
++				krb5_deltat remaining =
++				    cred.times.endtime - now;
++				if (remaining > 0 &&
++				    (min_lifetime == GSS_C_INDEFINITE ||
++				     (krb5_deltat)min_lifetime <= remaining))
++					found[i] = 1;
++			}
++			krb5_free_principal(ctx, svc_princ);
++		}
++		krb5_free_cred_contents(ctx, &cred);
++	}
++	krb5_cc_end_seq_get(ctx, cc, &cursor);
++	krb5_cc_close(ctx, cc);
++	cc = NULL;
++
++	all_found = 1;
++	for (i = 0; i < nservices; i++) {
++		if (!found[i]) {
++			all_found = 0;
++			break;
++		}
++	}
++out:
++	free(found);
++	if (ctx != NULL)
++		krb5_free_context(ctx);
++	return all_found;
++}
++
++/* As user - called on fatal/exit */
++void
++ssh_gssapi_cleanup_creds(void)
++{
++	ssh_gssapi_ccache *store = ssh_gssapi_get_ccache();
++	krb5_ccache ccache = NULL;
++	krb5_error_code problem;
++
++	if (store->data != NULL) {
++		if ((problem = krb5_cc_resolve(store->data,
++		    store->envval, &ccache))) {
++			debug_f("krb5_cc_resolve(): %.100s",
++			    krb5_get_err_text(store->data, problem));
++		} else if ((problem = krb5_cc_destroy(store->data, ccache))) {
++			debug_f("krb5_cc_destroy(): %.100s",
++			    krb5_get_err_text(store->data, problem));
++		} else {
++			krb5_free_context(store->data);
++			store->data = NULL;
++		}
++	}
++}
++
++/*
++ * Filter the user's ccache by removing the ticket classes indicated by
++ * drop_flags (SSH_GSSAPI_CCFILTER_* bitmask).  Each credential is
++ * categorised as one of:
++ *   TGT   - server principal matches "krbtgt/" prefix
++ *   PROXY - server principal matches one of proxy_services[]
++ *   SELF  - everything else (the S4U2Self evidence ticket)
++ * Credentials in a flagged category are discarded; the rest are written
++ * back after reinitialising the ccache.  Runs as user.
++ */
++void
++ssh_gssapi_krb5_filter_ccache(u_int drop_flags,
++    char **proxy_services, u_int nproxy_services)
++{
++	ssh_gssapi_ccache *store = ssh_gssapi_get_ccache();
++	krb5_context ctx = (krb5_context)store->data;
++	krb5_ccache cc = NULL;
++	krb5_cc_cursor cursor;
++	krb5_creds *keep = NULL;
++	krb5_principal princ = NULL;
++	krb5_error_code problem;
++	char *srvname;
++	u_int i, nkeep = 0, cap = 0;
++	int is_tgt, is_proxy, drop;
++
++	if (ctx == NULL || store->envval == NULL)
++		return;
++
++	if ((problem = krb5_cc_resolve(ctx, store->envval, &cc)) != 0) {
++		debug_f("krb5_cc_resolve: %.100s",
++		    krb5_get_err_text(ctx, problem));
++		return;
++	}
++	if ((problem = krb5_cc_get_principal(ctx, cc, &princ)) != 0) {
++		debug_f("krb5_cc_get_principal: %.100s",
++		    krb5_get_err_text(ctx, problem));
++		krb5_cc_close(ctx, cc);
++		return;
++	}
++	if ((problem = krb5_cc_start_seq_get(ctx, cc, &cursor)) != 0) {
++		debug_f("krb5_cc_start_seq_get: %.100s",
++		    krb5_get_err_text(ctx, problem));
++		krb5_free_principal(ctx, princ);
++		krb5_cc_close(ctx, cc);
++		return;
++	}
++
++	{
++		krb5_creds cred;
++		krb5_principal svc_princ;
++		while (krb5_cc_next_cred(ctx, cc, &cursor, &cred) == 0) {
++			is_tgt = is_proxy = 0;
++			if (krb5_unparse_name(ctx, cred.server,
++			    &srvname) == 0) {
++				is_tgt = strncmp(srvname, "krbtgt/", 7) == 0;
++				krb5_free_unparsed_name(ctx, srvname);
++			}
++			if (!is_tgt) {
++				/*
++				 * Use krb5_principal_compare() rather than
++				 * strcmp() so that a service name configured
++				 * without an explicit realm (krb5_parse_name
++				 * appends the default realm) still matches
++				 * the fully-qualified name in the ccache.
++				 */
++				for (i = 0; i < nproxy_services; i++) {
++					if (krb5_parse_name(ctx,
++					    proxy_services[i], &svc_princ) != 0)
++						continue;
++					if (krb5_principal_compare(ctx,
++					    cred.server, svc_princ))
++						is_proxy = 1;
++					krb5_free_principal(ctx, svc_princ);
++					if (is_proxy)
++						break;
++				}
++			}
++			if (is_tgt)
++				drop = drop_flags & SSH_GSSAPI_CCFILTER_TGT;
++			else if (is_proxy)
++				drop = drop_flags & SSH_GSSAPI_CCFILTER_PROXY;
++			else
++				drop = drop_flags & SSH_GSSAPI_CCFILTER_SELF;
++
++			if (!drop) {
++				if (nkeep >= cap) {
++					cap = cap ? cap * 2 : 4;
++					keep = xreallocarray(keep, cap,
++					    sizeof(*keep));
++				}
++				keep[nkeep++] = cred;
++			} else
++				krb5_free_cred_contents(ctx, &cred);
++		}
++	}
++	krb5_cc_end_seq_get(ctx, cc, &cursor);
++
++	if ((problem = krb5_cc_initialize(ctx, cc, princ)) != 0) {
++		logit_f("krb5_cc_initialize: %.100s",
++		    krb5_get_err_text(ctx, problem));
++	} else {
++		for (i = 0; i < nkeep; i++)
++			krb5_cc_store_cred(ctx, cc, &keep[i]);
++		debug_f("ccache filter 0x%x: retained %u ticket(s)",
++		    drop_flags, nkeep);
++	}
++
++	for (i = 0; i < nkeep; i++)
++		krb5_free_cred_contents(ctx, &keep[i]);
++	free(keep);
++	krb5_free_principal(ctx, princ);
++	krb5_cc_close(ctx, cc);
++}
++
+ ssh_gssapi_mech gssapi_kerberos_mech = {
+ 	"toWM5Slw5Ew8Mqkay+al2g==",
+ 	"Kerberos",
+diff --git a/gss-serv.c b/gss-serv.c
+index 237a3c40a..d7206499d 100644
+--- a/gss-serv.c
++++ b/gss-serv.c
+@@ -55,7 +55,7 @@ extern ServerOptions options;
+ 
+ static ssh_gssapi_client gssapi_client =
+     { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
+-    GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL};
++    GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL, 0};
+ 
+ ssh_gssapi_mech gssapi_null_mech =
+     { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
+@@ -492,26 +492,382 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
+ 	return (ctx->major);
+ }
+ 
+-/* As user - called on fatal/exit */
++/* Returns non-zero if Kerberos credentials have already been stored. */
++int
++ssh_gssapi_credentials_stored(void)
++{
++	return gssapi_client.store.envval != NULL;
++}
++
++/* Returns a pointer to the credential-cache descriptor for this session. */
++ssh_gssapi_ccache *
++ssh_gssapi_get_ccache(void)
++{
++	return &gssapi_client.store;
++}
++
++/* Log human-readable GSSAPI major and minor status strings. */
++static void
++log_gss_error(OM_uint32 major, OM_uint32 minor, const char *label)
++{
++	OM_uint32 lmin, mctx;
++	gss_buffer_desc emsg = GSS_C_EMPTY_BUFFER;
++
++	mctx = 0;
++	do {
++		gss_display_status(&lmin, major, GSS_C_GSS_CODE,
++		    GSS_C_NO_OID, &mctx, &emsg);
++		logit("%s: %.*s", label, (int)emsg.length, (char *)emsg.value);
++		gss_release_buffer(&lmin, &emsg);
++	} while (mctx != 0);
++
++	mctx = 0;
++	do {
++		gss_display_status(&lmin, minor, GSS_C_MECH_CODE,
++		    &gssapi_kerberos_mech.oid, &mctx, &emsg);
++		if (emsg.length > 0)
++			logit("%s: %.*s", label,
++			    (int)emsg.length, (char *)emsg.value);
++		gss_release_buffer(&lmin, &emsg);
++	} while (mctx != 0);
++}
++
++/* Log the canonical string form of a GSSAPI name as a debug message. */
++static void
++debug_gss_name(const char *label, gss_name_t name)
++{
++	OM_uint32 lmin;
++	gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
++
++	if (gss_display_name(&lmin, name, &buf, NULL) == GSS_S_COMPLETE) {
++		debug_f("%s: %.*s", label, (int)buf.length, (char *)buf.value);
++		gss_release_buffer(&lmin, &buf);
++	}
++}
++
++/*
++ * Check whether the user already has valid GSSAPI initiator credentials
++ * (e.g. a Kerberos TGT) in their default credential store with at least
++ * min_lifetime seconds remaining.  Pass GSS_C_INDEFINITE to accept any
++ * positive remaining lifetime.  Runs as the user.
++ * Returns 1 if sufficient credentials exist, 0 otherwise.
++ */
++int
++ssh_gssapi_user_has_valid_tgt(u_int min_lifetime)
++{
++	OM_uint32 major, minor, lifetime = 0;
++	gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
++	int found = 0;
++
++	major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
++	    GSS_C_NO_OID_SET, GSS_C_INITIATE, &cred, NULL, &lifetime);
++	if (!GSS_ERROR(major) && lifetime > 0 &&
++	    (min_lifetime == GSS_C_INDEFINITE || lifetime >= min_lifetime))
++		found = 1;
++	if (cred != GSS_C_NO_CREDENTIAL)
++		gss_release_cred(&minor, &cred);
++	return found;
++}
++
++
++/*
++ * Perform S4U2Self (protocol transition): acquire a Kerberos service ticket
++ * for the SSH user on behalf of the host principal.  Runs privileged.
++ * Populates gssapi_client.{creds,mech,displayname,exportedname} on success.
++ * Returns 0 on success, -1 on failure.
++ */
++/* Privileged */
++int
++ssh_gssapi_s4u2self(const char *user, u_int lifetime)
++{
++	OM_uint32 major, minor, status;
++	gss_OID_set oidset = GSS_C_NO_OID_SET;
++	gss_name_t host_name = GSS_C_NO_NAME;
++	gss_name_t user_name = GSS_C_NO_NAME;
++	gss_cred_id_t host_creds = GSS_C_NO_CREDENTIAL;
++	gss_cred_id_t impersonated_creds = GSS_C_NO_CREDENTIAL;
++	gss_buffer_desc gssbuf, displayname = GSS_C_EMPTY_BUFFER;
++	char lname[NI_MAXHOST];
++	char *val;
++
++	if (gethostname(lname, sizeof(lname)) != 0) {
++		logit_f("gethostname: %s", strerror(errno));
++		return -1;
++	}
++
++	/* Acquire acceptor credential for host/ from the keytab */
++	gss_create_empty_oid_set(&status, &oidset);
++	gss_add_oid_set_member(&status, &gssapi_kerberos_mech.oid, &oidset);
++
++	xasprintf(&val, "host@%s", lname);
++	gssbuf.value = val;
++	gssbuf.length = strlen(val);
++	major = gss_import_name(&minor, &gssbuf,
++	    GSS_C_NT_HOSTBASED_SERVICE, &host_name);
++	free(val);
++	if (GSS_ERROR(major)) {
++		logit_f("gss_import_name (host) failed");
++		gss_release_oid_set(&status, &oidset);
++		return -1;
++	}
++	debug_gss_name("host name parsed as", host_name);
++
++	debug_f("acquiring host credentials as uid=%u euid=%u, principal=host@%s",
++	    (unsigned)getuid(), (unsigned)geteuid(), lname);
++	#ifdef HAVE_GSS_ACQUIRE_CRED_FROM
++	{
++# if defined(KRB5)
++		/*
++		 * Resolve the keytab path: krb5_kt_default_name respects
++		 * KRB5_KTNAME and krb5.conf default_keytab_name.
++		 */
++		char keytab_name[MAXPATHLEN];
++		krb5_context tmp_ctx;
++
++
++		keytab_name[0] = '\0';
++		if (krb5_init_context(&tmp_ctx) == 0) {
++			(void)krb5_kt_default_name(tmp_ctx, keytab_name,
++			    sizeof(keytab_name));
++			krb5_free_context(tmp_ctx);
++		}
++		if (keytab_name[0] == '\0')
++			strlcpy(keytab_name, "FILE:/etc/krb5.keytab",
++			    sizeof(keytab_name));
++		/*
++		 * client_keytab lets GSSAPI do AS-REQ to obtain a TGT for the
++		 * host principal (initiator role needed for S4U2Self).
++		 * keytab covers the acceptor role.
++		 * ccache: MEMORY: keeps the resulting TGT volatile.
++		 */
++		gss_key_value_element_desc store_elements[] = {
++			{ "client_keytab", keytab_name },
++			{ "keytab", keytab_name },
++			{ "ccache", "MEMORY:" },
++		};
++		const gss_key_value_set_desc cred_store = { 3, store_elements };
++# else
++		gss_key_value_element_desc store_elements[] = {
++			{ "ccache", "MEMORY:" },
++		};
++		const gss_key_value_set_desc cred_store = { 1, store_elements };
++# endif
++
++
++		major = gss_acquire_cred_from(&minor, host_name, lifetime,
++		    oidset, GSS_C_BOTH, &cred_store, &host_creds, NULL, NULL);
++	}
++#else
++	major = gss_acquire_cred(&minor, host_name, lifetime,
++	    oidset, GSS_C_BOTH, &host_creds, NULL, NULL);
++#endif
++	gss_release_name(&minor, &host_name);
++	if (GSS_ERROR(major)) {
++		logit_f("gss_acquire_cred(host@%s) failed as uid=%u euid=%u",
++		    lname, (unsigned)getuid(), (unsigned)geteuid());
++		log_gss_error(major, minor, "S4U2Self: gss_acquire_cred");
++		gss_release_oid_set(&status, &oidset);
++		return -1;
++	}
++
++	/* Import the SSH username as a GSSAPI/Kerberos name */
++	gssbuf.value = (void *)user;
++	gssbuf.length = strlen(user);
++	major = gss_import_name(&minor, &gssbuf,
++	    GSS_C_NT_USER_NAME, &user_name);
++	if (GSS_ERROR(major)) {
++		logit_f("gss_import_name (user) failed");
++		gss_release_cred(&minor, &host_creds);
++		gss_release_oid_set(&status, &oidset);
++		return -1;
++	}
++	debug_gss_name("user name parsed as", user_name);
++
++	/* S4U2Self: obtain a service ticket for the user without their creds */
++	debug_f("calling gss_acquire_cred_impersonate_name for user %.100s", user);
++	major = gss_acquire_cred_impersonate_name(&minor,
++	    host_creds, user_name, lifetime,
++	    oidset, GSS_C_INITIATE,
++	    &impersonated_creds, NULL, NULL);
++
++	gss_release_cred(&minor, &host_creds);
++	gss_release_oid_set(&status, &oidset);
++	if (GSS_ERROR(major)) {
++		logit_f("gss_acquire_cred_impersonate_name failed for %.100s",
++		    user);
++		log_gss_error(major, minor,
++		    "S4U2Self: gss_acquire_cred_impersonate_name");
++		gss_release_name(&minor, &user_name);
++		return -1;
++	}
++
++	/* Get the display name (Kerberos principal string) for storecreds */
++	major = gss_display_name(&minor, user_name, &displayname, NULL);
++	gss_release_name(&minor, &user_name);
++	if (GSS_ERROR(major)) {
++		logit_f("gss_display_name failed");
++		gss_release_cred(&minor, &impersonated_creds);
++		return -1;
++	}
++
++	/* Populate gssapi_client for storecreds_s4u2self and s4u2proxy */
++	gssapi_client.mech = &gssapi_kerberos_mech;
++	gssapi_client.creds = impersonated_creds;
++	gssapi_client.displayname.value = xmalloc(displayname.length + 1);
++	memcpy(gssapi_client.displayname.value,
++	    displayname.value, displayname.length);
++	((char *)gssapi_client.displayname.value)[displayname.length] = '\0';
++	gssapi_client.displayname.length = displayname.length;
++	/*
++	 * exportedname is used by ssh_gssapi_krb5_storecreds → krb5_parse_name.
++	 * gss_display_name for a user-name returns the canonical principal
++	 * string (e.g. user@REALM) which krb5_parse_name can consume directly.
++	 */
++	gssapi_client.exportedname.value = xmalloc(displayname.length + 1);
++	memcpy(gssapi_client.exportedname.value,
++	    displayname.value, displayname.length);
++	((char *)gssapi_client.exportedname.value)[displayname.length] = '\0';
++	gssapi_client.exportedname.length = displayname.length;
++
++	gss_release_buffer(&minor, &displayname);
++	debug_f("S4U2Self succeeded for %.100s", user);
++	return 0;
++}
++
++/* As user — write the S4U2Self ticket into a new ccache via mech->storecreds */
+ void
+-ssh_gssapi_cleanup_creds(void)
++ssh_gssapi_storecreds_s4u2self(void)
+ {
+-	krb5_ccache ccache = NULL;
+-	krb5_error_code problem;
+-
+-	if (gssapi_client.store.data != NULL) {
+-		if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) {
+-			debug_f("krb5_cc_resolve(): %.100s",
+-				krb5_get_err_text(gssapi_client.store.data, problem));
+-		} else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) {
+-			debug_f("krb5_cc_destroy(): %.100s",
+-				krb5_get_err_text(gssapi_client.store.data, problem));
+-		} else {
+-			krb5_free_context(gssapi_client.store.data);
+-			gssapi_client.store.data = NULL;
++	if (gssapi_client.mech == NULL || gssapi_client.mech->storecreds == NULL) {
++		debug_f("no GSSAPI mechanism for storing S4U2Self credentials");
++		return;
++	}
++	(*gssapi_client.mech->storecreds)(&gssapi_client);
++}
++
++/*
++ * Perform S4U2Proxy for each configured service principal, then flush all
++ * resulting tickets into the user's ccache.  Runs as user, after
++ * ssh_gssapi_storecreds_s4u2self() has created the ccache.
++ *
++ * gssapi_client.creds (the S4U2Self proxy credential) is passed as the
++ * initiator to gss_init_sec_context(); the GSSAPI library presents the TGT
++ * and evidence ticket to the KDC via S4U2Proxy TGS-REQ.  The output token
++ * (AP-REQ) is discarded — we do not connect to the target service.
++ *
++ * After iterating all services, gss_store_cred() flushes the accumulated
++ * proxy service tickets from the credential's internal ccache into the
++ * KRB5CCNAME ccache that storecreds_s4u2self() already created.
++ */
++/* As user */
++void
++ssh_gssapi_s4u2proxy(char **services, u_int nservices, u_int lifetime)
++{
++	OM_uint32 major, minor;
++	gss_buffer_desc service_buf, output_token = GSS_C_EMPTY_BUFFER;
++	gss_name_t target_name;
++	gss_ctx_id_t ctx;
++	u_int i;
++
++	if (gssapi_client.creds == GSS_C_NO_CREDENTIAL) {
++		debug_f("no proxy credential available");
++		return;
++	}
++	if (gssapi_client.store.envval == NULL) {
++		debug_f("no ccache path set; cannot store proxy tickets");
++		return;
++	}
++
++	debug_f("starting S4U2Proxy as uid=%u euid=%u, %u service(s), ccache=%s",
++	    (unsigned)getuid(), (unsigned)geteuid(), nservices,
++	    gssapi_client.store.envval);
++
++	/* Point the GSSAPI library at the user's ccache for ticket storage */
++	setenv("KRB5CCNAME", gssapi_client.store.envval, 1);
++
++	for (i = 0; i < nservices; i++) {
++		ctx = GSS_C_NO_CONTEXT;
++		target_name = GSS_C_NO_NAME;
++
++		service_buf.value = services[i];
++		service_buf.length = strlen(services[i]);
++
++		/*
++		 * GSS_C_NO_OID: let the library determine the name type.
++		 * With Kerberos as the active mechanism, a fully-qualified
++		 * principal like "svc/host@REALM" is parsed correctly.
++		 */
++		major = gss_import_name(&minor, &service_buf,
++		    GSS_C_NO_OID, &target_name);
++		if (GSS_ERROR(major)) {
++			logit_f("gss_import_name failed for %.200s",
++			    services[i]);
++			log_gss_error(major, minor, "S4U2Proxy: gss_import_name");
++			continue;
+ 		}
++		debug_gss_name("target service name parsed as", target_name);
++
++		debug_f("calling gss_init_sec_context for %.200s", services[i]);
++		major = gss_init_sec_context(&minor,
++		    gssapi_client.creds,		/* proxy credential */
++		    &ctx, target_name,
++		    GSS_C_NO_OID,			/* default mech (Kerberos) */
++		    0,					/* no flags, no mutual auth */
++		    lifetime,
++		    GSS_C_NO_CHANNEL_BINDINGS,
++		    GSS_C_NO_BUFFER,			/* no input token */
++		    NULL,				/* actual_mech_type */
++		    &output_token,
++		    NULL,				/* ret_flags */
++		    NULL);				/* time_rec */
++
++		gss_release_buffer(&minor, &output_token);
++		gss_release_name(&minor, &target_name);
++		if (ctx != GSS_C_NO_CONTEXT)
++			gss_delete_sec_context(&minor, &ctx, GSS_C_NO_BUFFER);
++
++		if (GSS_ERROR(major)) {
++			logit_f("S4U2Proxy for %.200s on behalf of %.200s failed",
++			    services[i],
++			    (char *)gssapi_client.displayname.value);
++			log_gss_error(major, minor,
++			    "S4U2Proxy: gss_init_sec_context");
++		} else
++			debug_f("S4U2Proxy ticket obtained for %.200s",
++			    services[i]);
+ 	}
++
++	/*
++	 * Flush all proxy service tickets from the credential's internal
++	 * ccache into the KRB5CCNAME ccache via gss_store_cred().
++	 */
++	major = gss_store_cred(&minor, gssapi_client.creds, GSS_C_INITIATE,
++	    GSS_C_NO_OID, 1 /* overwrite_cred */, 1 /* default_cred */,
++	    NULL, NULL);
++	if (GSS_ERROR(major)) {
++		logit_f("gss_store_cred failed; proxy tickets may be missing");
++		log_gss_error(major, minor, "S4U2Proxy: gss_store_cred");
++	}
++
++	unsetenv("KRB5CCNAME");
++}
++
++#ifndef KRB5
++/* As user - called on fatal/exit; full implementation in gss-serv-krb5.c */
++void
++ssh_gssapi_cleanup_creds(void)
++{
++}
++
++/*
++ * Filter the user's ccache; full implementation in gss-serv-krb5.c.
++ */
++void
++ssh_gssapi_krb5_filter_ccache(u_int drop_flags,
++    char **proxy_services, u_int nproxy_services)
++{
+ }
++#endif /* !KRB5 */
+ 
+ /* As user */
+ int
+diff --git a/servconf.c b/servconf.c
+index 03b84ecb8..ab7327a2a 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -1500,6 +1500,43 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+ 		if (options->gss_indicators == NULL)
+ 			options->gss_indicators = xstrdup(arg);
+ 		break;
++
++	case sGSSAPIAllowS4U2Self:
++		arg = argv_next(&ac, &av);
++		if (!arg || *arg == '\0')
++			fatal("%s line %d: %s missing argument.",
++			    filename, linenum, keyword);
++		if (strcasecmp(arg, "no") == 0)
++			value = 0;
++		else if (strcasecmp(arg, "yes") == 0)
++			value = INT_MAX;
++		else if ((value = convtime(arg)) <= 0)
++			fatal("%s line %d: invalid %s value \"%s\".",
++			    filename, linenum, keyword, arg);
++		if (*activep && options->gss_allow_s4u2self == -1)
++			options->gss_allow_s4u2self = value;
++		break;
++
++	case sGSSAPIProxyS4U2Services:
++		while ((arg = argv_next(&ac, &av)) != NULL) {
++			if (*arg == '\0')
++				fatal("%s line %d: %s missing argument.",
++				    filename, linenum, keyword);
++			if (strcasecmp(arg, "none") == 0) {
++				for (i = 0; i < options->num_gss_proxy_services; i++)
++					free(options->gss_proxy_services[i]);
++				free(options->gss_proxy_services);
++				options->gss_proxy_services = NULL;
++				options->num_gss_proxy_services = 0;
++				break;
++			}
++			if (!*activep)
++				continue;
++			opt_array_append(filename, linenum, keyword,
++			    &options->gss_proxy_services,
++			    &options->num_gss_proxy_services, arg);
++		}
++		break;
+ #endif /* GSSAPI */
+ 
+ 	case sPasswordAuthentication:
+@@ -4291,6 +4328,15 @@ dump_config(ServerOptions *o)
+ 	dump_cfg_fmtint(sGSSAPIStoreCredentialsOnRekey, o->gss_store_rekey);
+ 	dump_cfg_string(sGSSAPIKexAlgorithms, o->gss_kex_algorithms);
+ 	dump_cfg_string(sGSSAPIIndicators, o->gss_indicators);
++	if (o->gss_allow_s4u2self == 0)
++		printf("%s no\n", lookup_opcode_name(sGSSAPIAllowS4U2Self));
++	else if (o->gss_allow_s4u2self == INT_MAX)
++		printf("%s yes\n", lookup_opcode_name(sGSSAPIAllowS4U2Self));
++	else
++		printf("%s %d\n", lookup_opcode_name(sGSSAPIAllowS4U2Self),
++		    o->gss_allow_s4u2self);
++	dump_cfg_strarray_oneline(sGSSAPIProxyS4U2Services,
++	    o->num_gss_proxy_services, o->gss_proxy_services);
+ #endif
+ 	dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
+ 	dump_cfg_fmtint(sKbdInteractiveAuthentication,
+diff --git a/servconf.h b/servconf.h
+index c1bbd82b8..61565f0ae 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -329,7 +329,9 @@ SSHCONF_INTFLAG(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE
+ SSHCONF_INTFLAG(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL, 0, SSHCFG_COPY_NONE) \
+ SSHCONF_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
+ SSHCONF_INTFLAG(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+-SSHCONF_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL, SSHCFG_COPY_MATCH)
++SSHCONF_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL, SSHCFG_COPY_MATCH) \
++SSHCONF_INTFLAG(gss_allow_s4u2self, GSSAPIAllowS4U2Self, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
++SSHCONF_STRARRAY(gss_proxy_services, num_gss_proxy_services, GSSAPIProxyS4U2Services, SSHCFG_ALL, SSHCFG_COPY_MATCH)
+ #else /* GSSAPI */
+ #define SSHD_CONFIG_ENTRIES_GSS \
+ SSHCONF_UNSUPPORTED_INT(gss_authentication, GSSAPIAuthentication, SSHCFG_ALL) \
+@@ -340,7 +342,9 @@ SSHCONF_UNSUPPORTED_INT(gss_keyex, GSSAPIKeyExchange, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(gss_store_rekey, GSSAPIStoreCredentialsOnRekey, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_STRING(gss_kex_algorithms, GSSAPIKexAlgorithms, SSHCFG_GLOBAL) \
+ SSHCONF_UNSUPPORTED_INT(enable_k5users, GSSAPIEnablek5users, SSHCFG_ALL) \
+-SSHCONF_UNSUPPORTED_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL)
++SSHCONF_UNSUPPORTED_STRING(gss_indicators, GSSAPIIndicators, SSHCFG_ALL) \
++SSHCONF_UNSUPPORTED_INT(gss_allow_s4u2self, GSSAPIAllowS4U2Self, SSHCFG_ALL) \
++SSHCONF_UNSUPPORTED_STRING(gss_proxy_services, GSSAPIProxyS4U2Services, SSHCFG_ALL)
+ #endif /* GSSAPI */
+ 
+ #define SSHD_CONFIG_ENTRIES \
+diff --git a/ssh-gss.h b/ssh-gss.h
+index 1506719a9..a0d51c9be 100644
+--- a/ssh-gss.h
++++ b/ssh-gss.h
+@@ -119,6 +119,7 @@ typedef struct {
+ 	int used;
+ 	int updated;
+ 	char **indicators; /* auth indicators */
++	int allow_self; /* allow protocol transition */
+ } ssh_gssapi_client;
+ 
+ typedef struct ssh_gssapi_mech_struct {
+@@ -199,6 +200,18 @@ OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
+ void ssh_gssapi_do_child(char ***, u_int *);
+ void ssh_gssapi_cleanup_creds(void);
+ int ssh_gssapi_storecreds(void);
++int  ssh_gssapi_credentials_stored(void);
++ssh_gssapi_ccache *ssh_gssapi_get_ccache(void);
++int  ssh_gssapi_user_has_valid_tgt(u_int);
++int  ssh_gssapi_user_has_valid_proxy_tickets(char **, u_int, u_int);
++int  ssh_gssapi_s4u2self(const char *, u_int);
++void ssh_gssapi_storecreds_s4u2self(void);
++void ssh_gssapi_s4u2proxy(char **, u_int, u_int);
++/* Flags for ssh_gssapi_krb5_filter_ccache(): which ticket classes to remove */
++#define SSH_GSSAPI_CCFILTER_TGT    (1u << 0) /* krbtgt/... entries */
++#define SSH_GSSAPI_CCFILTER_SELF   (1u << 1) /* S4U2Self evidence ticket */
++#define SSH_GSSAPI_CCFILTER_PROXY  (1u << 2) /* S4U2Proxy service tickets */
++void ssh_gssapi_krb5_filter_ccache(u_int, char **, u_int);
+ const char *ssh_gssapi_displayname(void);
+ 
+ char *ssh_gssapi_server_mechanisms(void);
+diff --git a/sshd-session.c b/sshd-session.c
+index 0284c8e42..29971dc70 100644
+--- a/sshd-session.c
++++ b/sshd-session.c
+@@ -1394,6 +1394,104 @@ main(int ac, char **av)
+ 		authctxt->krb5_set_env = ssh_gssapi_storecreds();
+ 		restore_uid();
+ 	}
++	/*
++	 * GSSAPIAllowS4U2Self / GSSAPIProxyS4U2Services: if no credentials were stored
++	 * above (i.e. no GSSAPI auth with delegation occurred), use S4U2Self
++	 * to obtain an impersonated credential for the user, then optionally
++	 * follow with S4U2Proxy for configured target services.
++	 *
++	 * GSSAPIAllowS4U2Self alone:    store S4U2Self evidence ticket only;
++	 *                            the host TGT is removed.
++	 * GSSAPIProxyS4U2Services alone: store host TGT and S4U2Proxy service
++	 *                            tickets; the S4U2Self evidence ticket
++	 *                            is removed.
++	 * Both:                     store host TGT, S4U2Self evidence ticket,
++	 *                            and all S4U2Proxy service tickets.
++	 *
++	 * When S4U2Proxy tickets are present the host TGT must remain in the
++	 * ccache; applications check for TGT presence to determine whether
++	 * Kerberos credentials are available.  Only in GSSAPIAllowS4U2Self-alone
++	 * mode (no proxy tickets) is the host TGT removed.
++	 *
++	 * Skip S4U2Self when the user already has credentials covering the
++	 * requested lifetime: check for a valid TGT in the GSSAPIAllowS4U2Self-
++	 * alone case, or for valid proxy tickets for every configured service
++	 * otherwise.
++	 */
++	if ((options.gss_allow_s4u2self || options.num_gss_proxy_services > 0) &&
++	    !ssh_gssapi_credentials_stored()) {
++		u_int lifetime = (!options.gss_allow_s4u2self ||
++		    options.gss_allow_s4u2self == INT_MAX) ?
++		    GSS_C_INDEFINITE : (u_int)options.gss_allow_s4u2self;
++		int skip = 0;
++
++		temporarily_use_uid(authctxt->pw);
++		if (options.gss_allow_s4u2self &&
++		    options.num_gss_proxy_services == 0) {
++			/* S4U2Self-alone: skip if user already has a valid TGT */
++			skip = ssh_gssapi_user_has_valid_tgt(lifetime);
++		} else if (options.num_gss_proxy_services > 0) {
++			/*
++			 * Proxy-only or both: skip if every configured service
++			 * already has a valid ticket in the user's ccache.
++			 * Service tickets are not GSSAPI initiator credentials,
++			 * so gss_acquire_cred() cannot be used; iterate the
++			 * ccache with the krb5 API instead.
++			 */
++			skip = ssh_gssapi_user_has_valid_proxy_tickets(
++			    options.gss_proxy_services,
++			    options.num_gss_proxy_services,
++			    lifetime);
++		}
++		restore_uid();
++
++		if (skip) {
++			debug_f("user %.100s already has valid Kerberos "
++			    "credentials, skipping S4U2Self",
++			    authctxt->user);
++		} else if (ssh_gssapi_s4u2self(authctxt->user, lifetime) == 0) {
++			u_int filter;
++
++			temporarily_use_uid(authctxt->pw);
++			/*
++			 * Always create the ccache via storecreds_s4u2self so
++			 * that s4u2proxy has a ccache to store tickets into.
++			 * gss_krb5_copy_ccache() copies the host service's own
++			 * TGT along with the evidence ticket; filter_ccache
++			 * removes the ticket classes that should not be kept.
++			 */
++			ssh_gssapi_storecreds_s4u2self();
++			if (options.num_gss_proxy_services > 0)
++				ssh_gssapi_s4u2proxy(
++				    options.gss_proxy_services,
++				    options.num_gss_proxy_services,
++				    lifetime);
++
++			/*
++			 * Remove the host TGT only in GSSAPIAllowS4U2Self-alone
++			 * mode; when proxy tickets are present the TGT must
++			 * stay so that applications recognise the ccache as
++			 * holding live Kerberos credentials.
++			 * Remove the S4U2Self evidence ticket in proxy-only
++			 * mode (GSSAPIProxyS4U2Services without GSSAPIAllowS4U2Self).
++			 */
++			filter = 0;
++			if (options.gss_allow_s4u2self &&
++			    options.num_gss_proxy_services == 0)
++				filter = SSH_GSSAPI_CCFILTER_TGT |
++				    SSH_GSSAPI_CCFILTER_PROXY;
++			else if (!options.gss_allow_s4u2self)
++				filter = SSH_GSSAPI_CCFILTER_SELF;
++			if (filter != 0)
++				ssh_gssapi_krb5_filter_ccache(filter,
++				    options.gss_proxy_services,
++				    options.num_gss_proxy_services);
++			restore_uid();
++		} else {
++			logit("S4U2Self failed for user %.100s, continuing",
++			    authctxt->user);
++		}
++	}
+ #endif
+ #ifdef WITH_SELINUX
+ 	sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
+diff --git a/sshd_config.5 b/sshd_config.5
+index 8cbe5ecba..c16d7f5b0 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -857,6 +857,76 @@ FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens
+ The default
+ .Dq none
+ is to not use GSSAPI authentication indicators for access decisions.
++.It Cm GSSAPIAllowS4U2Self
++Controls whether the SSH server performs a Kerberos protocol transition
++(S4U2Self) after a successful authentication using any other method.
++Accepted values are
++.Cm no ,
++.Cm yes ,
++or a time interval (see
++.Sx TIME FORMATS
++below).
++.Cm no
++disables S4U2Self entirely.
++.Cm yes
++enables S4U2Self and requests a ticket with the maximum lifetime
++permitted by the KDC.
++A time interval (e.g.\&
++.Cm 8h ,
++.Cm 1d )
++enables S4U2Self and requests a ticket valid for at most that duration.
++The option is a no-op when delegated GSSAPI credentials are already available.
++The obtained service ticket is stored in the default credentials cache and is
++accessible to any application that has access to the Kerberos host principal
++.Pq host/machine.fqdn@REALM
++credentials on the same host.
++.Pp
++The default is
++.Cm no .
++.It Cm GSSAPIProxyS4U2Services
++Specifies a list of Kerberos service principals for which constrained
++delegation (S4U2Proxy) tickets should be obtained after a successful
++S4U2Self protocol transition.
++Each entry must be a fully-qualified Kerberos principal name of the form
++.Ar service/host@REALM .
++Multiple principals may be listed, separated by whitespace.
++The keyword
++.Cm none
++clears any previously set list.
++.Pp
++This option may be used independently of
++.Cm GSSAPIAllowS4U2Self .
++When S4U2Self succeeds, the server iterates the list and calls
++.Xr gss_init_sec_context 3
++for each principal with the proxy credential obtained by S4U2Self as
++the initiator.
++The GSSAPI library presents the evidence ticket to the KDC via an
++S4U2Proxy TGS-REQ; if the host service holds the necessary constrained-
++delegation permission in the KDC, a service ticket from the user to
++the target service is issued.
++These tickets are accumulated and then flushed into the user's ccache
++via
++.Xr gss_store_cred 3 ,
++so that any application running in the user's session can use them
++without further interaction.
++The AP-REQ output token of each
++.Xr gss_init_sec_context 3
++call is discarded; no network connection to the target service is made.
++.Pp
++When used together with
++.Cm GSSAPIAllowS4U2Self ,
++the TGT and S4U2Self ticket are also stored in the user's ccache in
++addition to the S4U2Proxy service tickets.
++When used alone (without
++.Cm GSSAPIAllowS4U2Self ) ,
++only the S4U2Proxy service tickets are stored; the intermediate S4U2Self
++credential is not placed in the user's ccache.
++.Pp
++This option supports
++.Cm Match
++blocks, allowing per-user or per-host lists of delegation targets.
++.Pp
++The default is empty (no S4U2Proxy delegation is performed).
+ .It Cm HostbasedAcceptedAlgorithms
+ The default is handled system-wide by
+ .Xr crypto-policies 7 .
+-- 
+2.55.0
+

diff --git a/0051-openssh-10.2p1-pam-auth.patch b/0051-openssh-10.2p1-pam-auth.patch
deleted file mode 100644
index 5128af3..0000000
--- a/0051-openssh-10.2p1-pam-auth.patch
+++ /dev/null
@@ -1,43 +0,0 @@
-From 04c13392280d1839b19cfe6e4dba48db3a8a8e77 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Sun, 12 Apr 2026 12:25:04 +0200
-Subject: [PATCH 51/54] openssh-10.2p1-pam-auth
-
-https://bugzilla.redhat.com/show_bug.cgi?id=2423900
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- auth-pam.c | 10 +++++-----
- 1 file changed, 5 insertions(+), 5 deletions(-)
-
-diff --git a/auth-pam.c b/auth-pam.c
-index d3dcd037b..aaad16765 100644
---- a/auth-pam.c
-+++ b/auth-pam.c
-@@ -455,6 +455,7 @@ static int
- check_pam_user(Authctxt *authctxt)
- {
- 	const char *pam_user;
-+	const struct passwd *pam_pw;
- 
- 	if (authctxt == NULL || authctxt->pw == NULL ||
- 	    authctxt->pw->pw_name == NULL)
-@@ -469,11 +470,10 @@ check_pam_user(Authctxt *authctxt)
- 		return PAM_USER_UNKNOWN;
- 	}
- 
--	if (sshpam_initial_user == NULL)
--		fatal_f("internal error: sshpam_initial_user NULL");
--	if (strcmp(sshpam_initial_user, pam_user) != 0) {
--		error_f("PAM user \"%s\" does not match previous \"%s\"",
--		      pam_user, sshpam_initial_user);
-+	pam_pw = getpwnam(pam_user);
-+	if (pam_pw == NULL || pam_pw->pw_uid != authctxt->pw->pw_uid) {
-+		debug("PAM user \"%s\" does not match expected \"%s\"",
-+		      pam_user, authctxt->pw->pw_name);
- 		return PAM_USER_UNKNOWN;
- 	}
- 	return PAM_SUCCESS;
--- 
-2.53.0
-

diff --git a/0052-gssapi-s4u.patch b/0052-gssapi-s4u.patch
deleted file mode 100644
index 8b3b2c2..0000000
--- a/0052-gssapi-s4u.patch
+++ /dev/null
@@ -1,1077 +0,0 @@
-From f629beb0fb094ffea8327699a942ba3c180f5e04 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Sun, 12 Apr 2026 12:25:21 +0200
-Subject: [PATCH 52/54] gssapi-s4u
-
-Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
----
- auth-krb5.c     |  13 ++
- configure.ac    |  10 +-
- gss-serv-krb5.c | 225 ++++++++++++++++++++++++++++
- gss-serv.c      | 388 ++++++++++++++++++++++++++++++++++++++++++++++--
- servconf.c      |  58 ++++++++
- servconf.h      |   4 +
- ssh-gss.h       |  13 ++
- sshd-session.c  |  98 ++++++++++++
- sshd_config.5   |  70 +++++++++
- 9 files changed, 861 insertions(+), 18 deletions(-)
-
-diff --git a/auth-krb5.c b/auth-krb5.c
-index 287bdd35a..7d4fbcc3d 100644
---- a/auth-krb5.c
-+++ b/auth-krb5.c
-@@ -463,6 +463,19 @@ ssh_krb5_cc_new_unique(krb5_context ctx, krb5_ccache *ccache, int *need_environm
- 	 * a primary cache for this collection, if it supports that (non-FILE)
- 	 */
- 	if (krb5_cc_support_switch(ctx, type)) {
-+		/*
-+		 * For collection-type caches (KCM, KEYRING, …) reuse the
-+		 * existing primary ccache when one is already present.  The
-+		 * caller will reinitialise it with krb5_cc_initialize(), so
-+		 * its old contents are replaced rather than orphaned.  Only
-+		 * create a fresh unique ccache when no primary exists yet.
-+		 */
-+		if (krb5_cc_default(ctx, ccache) == 0) {
-+			debug3_f("reusing existing default ccache of type %s",
-+			    type);
-+			free(type);
-+			return 0;
-+		}
- 		debug3_f("calling cc_new_unique(%s)", ccname);
- 		ret = krb5_cc_new_unique(ctx, type, NULL, ccache);
- 		free(type);
-diff --git a/configure.ac b/configure.ac
-index 1388e5e72..62b2bf9e7 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -5145,11 +5145,17 @@ AC_ARG_WITH([kerberos5],
- # include <gssapi_generic.h>
- #elif defined(HAVE_GSSAPI_GSSAPI_GENERIC_H)
- # include <gssapi/gssapi_generic.h>
-+#endif
-+#ifdef HAVE_GSSAPI_EXT_H
-+# include <gssapi/gssapi_ext.h>
-+#endif
-+#ifdef HAVE_GSSAPI_KRB5_H
-+# include <gssapi/gssapi_krb5.h>
- #endif
- 		]])
- 		saved_LIBS="$LIBS"
--		LIBS="$LIBS $K5LIBS"
--		AC_CHECK_FUNCS([krb5_cc_new_unique krb5_get_error_message krb5_free_error_message])
-+		LIBS="$LIBS $GSSLIBS $K5LIBS "
-+		AC_CHECK_FUNCS([krb5_cc_new_unique krb5_get_error_message krb5_free_error_message gss_acquire_cred_from])
- 		LIBS="$saved_LIBS"
- 
- 	fi
-diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
-index fc7cabc09..06d450d4d 100644
---- a/gss-serv-krb5.c
-+++ b/gss-serv-krb5.c
-@@ -609,6 +609,231 @@ ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store,
- 	return 1;
- }
- 
-+/*
-+ * Check whether the user's default ccache already contains valid service
-+ * tickets for all principals listed in services[].  Returns 1 if every
-+ * listed service has a ticket with at least min_lifetime seconds remaining
-+ * (pass GSS_C_INDEFINITE to accept any positive remaining lifetime), 0 if
-+ * any ticket is missing or too close to expiry.  Runs as the user.
-+ *
-+ * Unlike ssh_gssapi_user_has_valid_tgt(), this cannot use gss_acquire_cred()
-+ * because service tickets are not initiator credentials — GSSAPI only
-+ * surfaces TGTs via that API.  We iterate the ccache with the krb5 API
-+ * directly instead.
-+ */
-+int
-+ssh_gssapi_user_has_valid_proxy_tickets(char **services, u_int nservices,
-+    u_int min_lifetime)
-+{
-+	krb5_context ctx = NULL;
-+	krb5_ccache cc = NULL;
-+	krb5_cc_cursor cursor;
-+	krb5_creds cred;
-+	krb5_principal svc_princ;
-+	int *found = NULL;
-+	u_int i;
-+	int all_found = 0;
-+	time_t now;
-+
-+	if (nservices == 0)
-+		return 0;
-+
-+	if (krb5_init_context(&ctx) != 0)
-+		return 0;
-+
-+	found = xcalloc(nservices, sizeof(*found));
-+	now = time(NULL);
-+
-+	if (krb5_cc_default(ctx, &cc) != 0)
-+		goto out;
-+
-+	if (krb5_cc_start_seq_get(ctx, cc, &cursor) != 0) {
-+		krb5_cc_close(ctx, cc);
-+		cc = NULL;
-+		goto out;
-+	}
-+
-+	while (krb5_cc_next_cred(ctx, cc, &cursor, &cred) == 0) {
-+		/*
-+		 * Use krb5_principal_compare() so that service names
-+		 * configured without an explicit realm (krb5_parse_name
-+		 * appends the default realm) still match the fully-
-+		 * qualified principal stored in the ccache.
-+		 */
-+		for (i = 0; i < nservices; i++) {
-+			if (found[i])
-+				continue;
-+			if (krb5_parse_name(ctx, services[i],
-+			    &svc_princ) != 0)
-+				continue;
-+			if (krb5_principal_compare(ctx,
-+			    cred.server, svc_princ)) {
-+				krb5_deltat remaining =
-+				    cred.times.endtime - now;
-+				if (remaining > 0 &&
-+				    (min_lifetime == GSS_C_INDEFINITE ||
-+				     (krb5_deltat)min_lifetime <= remaining))
-+					found[i] = 1;
-+			}
-+			krb5_free_principal(ctx, svc_princ);
-+		}
-+		krb5_free_cred_contents(ctx, &cred);
-+	}
-+	krb5_cc_end_seq_get(ctx, cc, &cursor);
-+	krb5_cc_close(ctx, cc);
-+	cc = NULL;
-+
-+	all_found = 1;
-+	for (i = 0; i < nservices; i++) {
-+		if (!found[i]) {
-+			all_found = 0;
-+			break;
-+		}
-+	}
-+out:
-+	free(found);
-+	if (ctx != NULL)
-+		krb5_free_context(ctx);
-+	return all_found;
-+}
-+
-+/* As user - called on fatal/exit */
-+void
-+ssh_gssapi_cleanup_creds(void)
-+{
-+	ssh_gssapi_ccache *store = ssh_gssapi_get_ccache();
-+	krb5_ccache ccache = NULL;
-+	krb5_error_code problem;
-+
-+	if (store->data != NULL) {
-+		if ((problem = krb5_cc_resolve(store->data,
-+		    store->envval, &ccache))) {
-+			debug_f("krb5_cc_resolve(): %.100s",
-+			    krb5_get_err_text(store->data, problem));
-+		} else if ((problem = krb5_cc_destroy(store->data, ccache))) {
-+			debug_f("krb5_cc_destroy(): %.100s",
-+			    krb5_get_err_text(store->data, problem));
-+		} else {
-+			krb5_free_context(store->data);
-+			store->data = NULL;
-+		}
-+	}
-+}
-+
-+/*
-+ * Filter the user's ccache by removing the ticket classes indicated by
-+ * drop_flags (SSH_GSSAPI_CCFILTER_* bitmask).  Each credential is
-+ * categorised as one of:
-+ *   TGT   - server principal matches "krbtgt/" prefix
-+ *   PROXY - server principal matches one of proxy_services[]
-+ *   SELF  - everything else (the S4U2Self evidence ticket)
-+ * Credentials in a flagged category are discarded; the rest are written
-+ * back after reinitialising the ccache.  Runs as user.
-+ */
-+void
-+ssh_gssapi_krb5_filter_ccache(u_int drop_flags,
-+    char **proxy_services, u_int nproxy_services)
-+{
-+	ssh_gssapi_ccache *store = ssh_gssapi_get_ccache();
-+	krb5_context ctx = (krb5_context)store->data;
-+	krb5_ccache cc = NULL;
-+	krb5_cc_cursor cursor;
-+	krb5_creds *keep = NULL;
-+	krb5_principal princ = NULL;
-+	krb5_error_code problem;
-+	char *srvname;
-+	u_int i, nkeep = 0, cap = 0;
-+	int is_tgt, is_proxy, drop;
-+
-+	if (ctx == NULL || store->envval == NULL)
-+		return;
-+
-+	if ((problem = krb5_cc_resolve(ctx, store->envval, &cc)) != 0) {
-+		debug_f("krb5_cc_resolve: %.100s",
-+		    krb5_get_err_text(ctx, problem));
-+		return;
-+	}
-+	if ((problem = krb5_cc_get_principal(ctx, cc, &princ)) != 0) {
-+		debug_f("krb5_cc_get_principal: %.100s",
-+		    krb5_get_err_text(ctx, problem));
-+		krb5_cc_close(ctx, cc);
-+		return;
-+	}
-+	if ((problem = krb5_cc_start_seq_get(ctx, cc, &cursor)) != 0) {
-+		debug_f("krb5_cc_start_seq_get: %.100s",
-+		    krb5_get_err_text(ctx, problem));
-+		krb5_free_principal(ctx, princ);
-+		krb5_cc_close(ctx, cc);
-+		return;
-+	}
-+
-+	{
-+		krb5_creds cred;
-+		krb5_principal svc_princ;
-+		while (krb5_cc_next_cred(ctx, cc, &cursor, &cred) == 0) {
-+			is_tgt = is_proxy = 0;
-+			if (krb5_unparse_name(ctx, cred.server,
-+			    &srvname) == 0) {
-+				is_tgt = strncmp(srvname, "krbtgt/", 7) == 0;
-+				krb5_free_unparsed_name(ctx, srvname);
-+			}
-+			if (!is_tgt) {
-+				/*
-+				 * Use krb5_principal_compare() rather than
-+				 * strcmp() so that a service name configured
-+				 * without an explicit realm (krb5_parse_name
-+				 * appends the default realm) still matches
-+				 * the fully-qualified name in the ccache.
-+				 */
-+				for (i = 0; i < nproxy_services; i++) {
-+					if (krb5_parse_name(ctx,
-+					    proxy_services[i], &svc_princ) != 0)
-+						continue;
-+					if (krb5_principal_compare(ctx,
-+					    cred.server, svc_princ))
-+						is_proxy = 1;
-+					krb5_free_principal(ctx, svc_princ);
-+					if (is_proxy)
-+						break;
-+				}
-+			}
-+			if (is_tgt)
-+				drop = drop_flags & SSH_GSSAPI_CCFILTER_TGT;
-+			else if (is_proxy)
-+				drop = drop_flags & SSH_GSSAPI_CCFILTER_PROXY;
-+			else
-+				drop = drop_flags & SSH_GSSAPI_CCFILTER_SELF;
-+
-+			if (!drop) {
-+				if (nkeep >= cap) {
-+					cap = cap ? cap * 2 : 4;
-+					keep = xreallocarray(keep, cap,
-+					    sizeof(*keep));
-+				}
-+				keep[nkeep++] = cred;
-+			} else
-+				krb5_free_cred_contents(ctx, &cred);
-+		}
-+	}
-+	krb5_cc_end_seq_get(ctx, cc, &cursor);
-+
-+	if ((problem = krb5_cc_initialize(ctx, cc, princ)) != 0) {
-+		logit_f("krb5_cc_initialize: %.100s",
-+		    krb5_get_err_text(ctx, problem));
-+	} else {
-+		for (i = 0; i < nkeep; i++)
-+			krb5_cc_store_cred(ctx, cc, &keep[i]);
-+		debug_f("ccache filter 0x%x: retained %u ticket(s)",
-+		    drop_flags, nkeep);
-+	}
-+
-+	for (i = 0; i < nkeep; i++)
-+		krb5_free_cred_contents(ctx, &keep[i]);
-+	free(keep);
-+	krb5_free_principal(ctx, princ);
-+	krb5_cc_close(ctx, cc);
-+}
-+
- ssh_gssapi_mech gssapi_kerberos_mech = {
- 	"toWM5Slw5Ew8Mqkay+al2g==",
- 	"Kerberos",
-diff --git a/gss-serv.c b/gss-serv.c
-index 686ac4eb3..26d0a13a6 100644
---- a/gss-serv.c
-+++ b/gss-serv.c
-@@ -55,7 +55,7 @@ extern ServerOptions options;
- 
- static ssh_gssapi_client gssapi_client =
-     { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, GSS_C_NO_CREDENTIAL,
--    GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL};
-+    GSS_C_NO_NAME, NULL, {NULL, NULL, NULL, NULL, NULL}, 0, 0, NULL, 0};
- 
- ssh_gssapi_mech gssapi_null_mech =
-     { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL};
-@@ -488,26 +488,382 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
- 	return (ctx->major);
- }
- 
--/* As user - called on fatal/exit */
-+/* Returns non-zero if Kerberos credentials have already been stored. */
-+int
-+ssh_gssapi_credentials_stored(void)
-+{
-+	return gssapi_client.store.envval != NULL;
-+}
-+
-+/* Returns a pointer to the credential-cache descriptor for this session. */
-+ssh_gssapi_ccache *
-+ssh_gssapi_get_ccache(void)
-+{
-+	return &gssapi_client.store;
-+}
-+
-+/* Log human-readable GSSAPI major and minor status strings. */
-+static void
-+log_gss_error(OM_uint32 major, OM_uint32 minor, const char *label)
-+{
-+	OM_uint32 lmin, mctx;
-+	gss_buffer_desc emsg = GSS_C_EMPTY_BUFFER;
-+
-+	mctx = 0;
-+	do {
-+		gss_display_status(&lmin, major, GSS_C_GSS_CODE,
-+		    GSS_C_NO_OID, &mctx, &emsg);
-+		logit("%s: %.*s", label, (int)emsg.length, (char *)emsg.value);
-+		gss_release_buffer(&lmin, &emsg);
-+	} while (mctx != 0);
-+
-+	mctx = 0;
-+	do {
-+		gss_display_status(&lmin, minor, GSS_C_MECH_CODE,
-+		    &gssapi_kerberos_mech.oid, &mctx, &emsg);
-+		if (emsg.length > 0)
-+			logit("%s: %.*s", label,
-+			    (int)emsg.length, (char *)emsg.value);
-+		gss_release_buffer(&lmin, &emsg);
-+	} while (mctx != 0);
-+}
-+
-+/* Log the canonical string form of a GSSAPI name as a debug message. */
-+static void
-+debug_gss_name(const char *label, gss_name_t name)
-+{
-+	OM_uint32 lmin;
-+	gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
-+
-+	if (gss_display_name(&lmin, name, &buf, NULL) == GSS_S_COMPLETE) {
-+		debug_f("%s: %.*s", label, (int)buf.length, (char *)buf.value);
-+		gss_release_buffer(&lmin, &buf);
-+	}
-+}
-+
-+/*
-+ * Check whether the user already has valid GSSAPI initiator credentials
-+ * (e.g. a Kerberos TGT) in their default credential store with at least
-+ * min_lifetime seconds remaining.  Pass GSS_C_INDEFINITE to accept any
-+ * positive remaining lifetime.  Runs as the user.
-+ * Returns 1 if sufficient credentials exist, 0 otherwise.
-+ */
-+int
-+ssh_gssapi_user_has_valid_tgt(u_int min_lifetime)
-+{
-+	OM_uint32 major, minor, lifetime = 0;
-+	gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
-+	int found = 0;
-+
-+	major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
-+	    GSS_C_NO_OID_SET, GSS_C_INITIATE, &cred, NULL, &lifetime);
-+	if (!GSS_ERROR(major) && lifetime > 0 &&
-+	    (min_lifetime == GSS_C_INDEFINITE || lifetime >= min_lifetime))
-+		found = 1;
-+	if (cred != GSS_C_NO_CREDENTIAL)
-+		gss_release_cred(&minor, &cred);
-+	return found;
-+}
-+
-+
-+/*
-+ * Perform S4U2Self (protocol transition): acquire a Kerberos service ticket
-+ * for the SSH user on behalf of the host principal.  Runs privileged.
-+ * Populates gssapi_client.{creds,mech,displayname,exportedname} on success.
-+ * Returns 0 on success, -1 on failure.
-+ */
-+/* Privileged */
-+int
-+ssh_gssapi_s4u2self(const char *user, u_int lifetime)
-+{
-+	OM_uint32 major, minor, status;
-+	gss_OID_set oidset = GSS_C_NO_OID_SET;
-+	gss_name_t host_name = GSS_C_NO_NAME;
-+	gss_name_t user_name = GSS_C_NO_NAME;
-+	gss_cred_id_t host_creds = GSS_C_NO_CREDENTIAL;
-+	gss_cred_id_t impersonated_creds = GSS_C_NO_CREDENTIAL;
-+	gss_buffer_desc gssbuf, displayname = GSS_C_EMPTY_BUFFER;
-+	char lname[NI_MAXHOST];
-+	char *val;
-+
-+	if (gethostname(lname, sizeof(lname)) != 0) {
-+		logit_f("gethostname: %s", strerror(errno));
-+		return -1;
-+	}
-+
-+	/* Acquire acceptor credential for host/ from the keytab */
-+	gss_create_empty_oid_set(&status, &oidset);
-+	gss_add_oid_set_member(&status, &gssapi_kerberos_mech.oid, &oidset);
-+
-+	xasprintf(&val, "host@%s", lname);
-+	gssbuf.value = val;
-+	gssbuf.length = strlen(val);
-+	major = gss_import_name(&minor, &gssbuf,
-+	    GSS_C_NT_HOSTBASED_SERVICE, &host_name);
-+	free(val);
-+	if (GSS_ERROR(major)) {
-+		logit_f("gss_import_name (host) failed");
-+		gss_release_oid_set(&status, &oidset);
-+		return -1;
-+	}
-+	debug_gss_name("host name parsed as", host_name);
-+
-+	debug_f("acquiring host credentials as uid=%u euid=%u, principal=host@%s",
-+	    (unsigned)getuid(), (unsigned)geteuid(), lname);
-+	#ifdef HAVE_GSS_ACQUIRE_CRED_FROM
-+	{
-+# if defined(KRB5)
-+		/*
-+		 * Resolve the keytab path: krb5_kt_default_name respects
-+		 * KRB5_KTNAME and krb5.conf default_keytab_name.
-+		 */
-+		char keytab_name[MAXPATHLEN];
-+		krb5_context tmp_ctx;
-+
-+
-+		keytab_name[0] = '\0';
-+		if (krb5_init_context(&tmp_ctx) == 0) {
-+			(void)krb5_kt_default_name(tmp_ctx, keytab_name,
-+			    sizeof(keytab_name));
-+			krb5_free_context(tmp_ctx);
-+		}
-+		if (keytab_name[0] == '\0')
-+			strlcpy(keytab_name, "FILE:/etc/krb5.keytab",
-+			    sizeof(keytab_name));
-+		/*
-+		 * client_keytab lets GSSAPI do AS-REQ to obtain a TGT for the
-+		 * host principal (initiator role needed for S4U2Self).
-+		 * keytab covers the acceptor role.
-+		 * ccache: MEMORY: keeps the resulting TGT volatile.
-+		 */
-+		gss_key_value_element_desc store_elements[] = {
-+			{ "client_keytab", keytab_name },
-+			{ "keytab", keytab_name },
-+			{ "ccache", "MEMORY:" },
-+		};
-+		const gss_key_value_set_desc cred_store = { 3, store_elements };
-+# else
-+		gss_key_value_element_desc store_elements[] = {
-+			{ "ccache", "MEMORY:" },
-+		};
-+		const gss_key_value_set_desc cred_store = { 1, store_elements };
-+# endif
-+
-+
-+		major = gss_acquire_cred_from(&minor, host_name, lifetime,
-+		    oidset, GSS_C_BOTH, &cred_store, &host_creds, NULL, NULL);
-+	}
-+#else
-+	major = gss_acquire_cred(&minor, host_name, lifetime,
-+	    oidset, GSS_C_BOTH, &host_creds, NULL, NULL);
-+#endif
-+	gss_release_name(&minor, &host_name);
-+	if (GSS_ERROR(major)) {
-+		logit_f("gss_acquire_cred(host@%s) failed as uid=%u euid=%u",
-+		    lname, (unsigned)getuid(), (unsigned)geteuid());
-+		log_gss_error(major, minor, "S4U2Self: gss_acquire_cred");
-+		gss_release_oid_set(&status, &oidset);
-+		return -1;
-+	}
-+
-+	/* Import the SSH username as a GSSAPI/Kerberos name */
-+	gssbuf.value = (void *)user;
-+	gssbuf.length = strlen(user);
-+	major = gss_import_name(&minor, &gssbuf,
-+	    GSS_C_NT_USER_NAME, &user_name);
-+	if (GSS_ERROR(major)) {
-+		logit_f("gss_import_name (user) failed");
-+		gss_release_cred(&minor, &host_creds);
-+		gss_release_oid_set(&status, &oidset);
-+		return -1;
-+	}
-+	debug_gss_name("user name parsed as", user_name);
-+
-+	/* S4U2Self: obtain a service ticket for the user without their creds */
-+	debug_f("calling gss_acquire_cred_impersonate_name for user %.100s", user);
-+	major = gss_acquire_cred_impersonate_name(&minor,
-+	    host_creds, user_name, lifetime,
-+	    oidset, GSS_C_INITIATE,
-+	    &impersonated_creds, NULL, NULL);
-+
-+	gss_release_cred(&minor, &host_creds);
-+	gss_release_oid_set(&status, &oidset);
-+	if (GSS_ERROR(major)) {
-+		logit_f("gss_acquire_cred_impersonate_name failed for %.100s",
-+		    user);
-+		log_gss_error(major, minor,
-+		    "S4U2Self: gss_acquire_cred_impersonate_name");
-+		gss_release_name(&minor, &user_name);
-+		return -1;
-+	}
-+
-+	/* Get the display name (Kerberos principal string) for storecreds */
-+	major = gss_display_name(&minor, user_name, &displayname, NULL);
-+	gss_release_name(&minor, &user_name);
-+	if (GSS_ERROR(major)) {
-+		logit_f("gss_display_name failed");
-+		gss_release_cred(&minor, &impersonated_creds);
-+		return -1;
-+	}
-+
-+	/* Populate gssapi_client for storecreds_s4u2self and s4u2proxy */
-+	gssapi_client.mech = &gssapi_kerberos_mech;
-+	gssapi_client.creds = impersonated_creds;
-+	gssapi_client.displayname.value = xmalloc(displayname.length + 1);
-+	memcpy(gssapi_client.displayname.value,
-+	    displayname.value, displayname.length);
-+	((char *)gssapi_client.displayname.value)[displayname.length] = '\0';
-+	gssapi_client.displayname.length = displayname.length;
-+	/*
-+	 * exportedname is used by ssh_gssapi_krb5_storecreds → krb5_parse_name.
-+	 * gss_display_name for a user-name returns the canonical principal
-+	 * string (e.g. user@REALM) which krb5_parse_name can consume directly.
-+	 */
-+	gssapi_client.exportedname.value = xmalloc(displayname.length + 1);
-+	memcpy(gssapi_client.exportedname.value,
-+	    displayname.value, displayname.length);
-+	((char *)gssapi_client.exportedname.value)[displayname.length] = '\0';
-+	gssapi_client.exportedname.length = displayname.length;
-+
-+	gss_release_buffer(&minor, &displayname);
-+	debug_f("S4U2Self succeeded for %.100s", user);
-+	return 0;
-+}
-+
-+/* As user — write the S4U2Self ticket into a new ccache via mech->storecreds */
- void
--ssh_gssapi_cleanup_creds(void)
-+ssh_gssapi_storecreds_s4u2self(void)
- {
--	krb5_ccache ccache = NULL;
--	krb5_error_code problem;
--
--	if (gssapi_client.store.data != NULL) {
--		if ((problem = krb5_cc_resolve(gssapi_client.store.data, gssapi_client.store.envval, &ccache))) {
--			debug_f("krb5_cc_resolve(): %.100s",
--				krb5_get_err_text(gssapi_client.store.data, problem));
--		} else if ((problem = krb5_cc_destroy(gssapi_client.store.data, ccache))) {
--			debug_f("krb5_cc_destroy(): %.100s",
--				krb5_get_err_text(gssapi_client.store.data, problem));
--		} else {
--			krb5_free_context(gssapi_client.store.data);
--			gssapi_client.store.data = NULL;
-+	if (gssapi_client.mech == NULL || gssapi_client.mech->storecreds == NULL) {
-+		debug_f("no GSSAPI mechanism for storing S4U2Self credentials");
-+		return;
-+	}
-+	(*gssapi_client.mech->storecreds)(&gssapi_client);
-+}
-+
-+/*
-+ * Perform S4U2Proxy for each configured service principal, then flush all
-+ * resulting tickets into the user's ccache.  Runs as user, after
-+ * ssh_gssapi_storecreds_s4u2self() has created the ccache.
-+ *
-+ * gssapi_client.creds (the S4U2Self proxy credential) is passed as the
-+ * initiator to gss_init_sec_context(); the GSSAPI library presents the TGT
-+ * and evidence ticket to the KDC via S4U2Proxy TGS-REQ.  The output token
-+ * (AP-REQ) is discarded — we do not connect to the target service.
-+ *
-+ * After iterating all services, gss_store_cred() flushes the accumulated
-+ * proxy service tickets from the credential's internal ccache into the
-+ * KRB5CCNAME ccache that storecreds_s4u2self() already created.
-+ */
-+/* As user */
-+void
-+ssh_gssapi_s4u2proxy(char **services, u_int nservices, u_int lifetime)
-+{
-+	OM_uint32 major, minor;
-+	gss_buffer_desc service_buf, output_token = GSS_C_EMPTY_BUFFER;
-+	gss_name_t target_name;
-+	gss_ctx_id_t ctx;
-+	u_int i;
-+
-+	if (gssapi_client.creds == GSS_C_NO_CREDENTIAL) {
-+		debug_f("no proxy credential available");
-+		return;
-+	}
-+	if (gssapi_client.store.envval == NULL) {
-+		debug_f("no ccache path set; cannot store proxy tickets");
-+		return;
-+	}
-+
-+	debug_f("starting S4U2Proxy as uid=%u euid=%u, %u service(s), ccache=%s",
-+	    (unsigned)getuid(), (unsigned)geteuid(), nservices,
-+	    gssapi_client.store.envval);
-+
-+	/* Point the GSSAPI library at the user's ccache for ticket storage */
-+	setenv("KRB5CCNAME", gssapi_client.store.envval, 1);
-+
-+	for (i = 0; i < nservices; i++) {
-+		ctx = GSS_C_NO_CONTEXT;
-+		target_name = GSS_C_NO_NAME;
-+
-+		service_buf.value = services[i];
-+		service_buf.length = strlen(services[i]);
-+
-+		/*
-+		 * GSS_C_NO_OID: let the library determine the name type.
-+		 * With Kerberos as the active mechanism, a fully-qualified
-+		 * principal like "svc/host@REALM" is parsed correctly.
-+		 */
-+		major = gss_import_name(&minor, &service_buf,
-+		    GSS_C_NO_OID, &target_name);
-+		if (GSS_ERROR(major)) {
-+			logit_f("gss_import_name failed for %.200s",
-+			    services[i]);
-+			log_gss_error(major, minor, "S4U2Proxy: gss_import_name");
-+			continue;
- 		}
-+		debug_gss_name("target service name parsed as", target_name);
-+
-+		debug_f("calling gss_init_sec_context for %.200s", services[i]);
-+		major = gss_init_sec_context(&minor,
-+		    gssapi_client.creds,		/* proxy credential */
-+		    &ctx, target_name,
-+		    GSS_C_NO_OID,			/* default mech (Kerberos) */
-+		    0,					/* no flags, no mutual auth */
-+		    lifetime,
-+		    GSS_C_NO_CHANNEL_BINDINGS,
-+		    GSS_C_NO_BUFFER,			/* no input token */
-+		    NULL,				/* actual_mech_type */
-+		    &output_token,
-+		    NULL,				/* ret_flags */
-+		    NULL);				/* time_rec */
-+
-+		gss_release_buffer(&minor, &output_token);
-+		gss_release_name(&minor, &target_name);
-+		if (ctx != GSS_C_NO_CONTEXT)
-+			gss_delete_sec_context(&minor, &ctx, GSS_C_NO_BUFFER);
-+
-+		if (GSS_ERROR(major)) {
-+			logit_f("S4U2Proxy for %.200s on behalf of %.200s failed",
-+			    services[i],
-+			    (char *)gssapi_client.displayname.value);
-+			log_gss_error(major, minor,
-+			    "S4U2Proxy: gss_init_sec_context");
-+		} else
-+			debug_f("S4U2Proxy ticket obtained for %.200s",
-+			    services[i]);
- 	}
-+
-+	/*
-+	 * Flush all proxy service tickets from the credential's internal
-+	 * ccache into the KRB5CCNAME ccache via gss_store_cred().
-+	 */
-+	major = gss_store_cred(&minor, gssapi_client.creds, GSS_C_INITIATE,
-+	    GSS_C_NO_OID, 1 /* overwrite_cred */, 1 /* default_cred */,
-+	    NULL, NULL);
-+	if (GSS_ERROR(major)) {
-+		logit_f("gss_store_cred failed; proxy tickets may be missing");
-+		log_gss_error(major, minor, "S4U2Proxy: gss_store_cred");
-+	}
-+
-+	unsetenv("KRB5CCNAME");
-+}
-+
-+#ifndef KRB5
-+/* As user - called on fatal/exit; full implementation in gss-serv-krb5.c */
-+void
-+ssh_gssapi_cleanup_creds(void)
-+{
-+}
-+
-+/*
-+ * Filter the user's ccache; full implementation in gss-serv-krb5.c.
-+ */
-+void
-+ssh_gssapi_krb5_filter_ccache(u_int drop_flags,
-+    char **proxy_services, u_int nproxy_services)
-+{
- }
-+#endif /* !KRB5 */
- 
- /* As user */
- int
-diff --git a/servconf.c b/servconf.c
-index f4d6b19dd..e05a31584 100644
---- a/servconf.c
-+++ b/servconf.c
-@@ -149,6 +149,9 @@ initialize_server_options(ServerOptions *options)
- 	options->gss_indicators = NULL;
- 	options->gss_store_rekey = -1;
- 	options->gss_kex_algorithms = NULL;
-+	options->gss_allow_s4u2self = -1;
-+	options->gss_proxy_services = NULL;
-+	options->num_gss_proxy_services = 0;
- 	options->use_kuserok = -1;
- 	options->enable_k5users = -1;
- 	options->password_authentication = -1;
-@@ -406,6 +409,8 @@ fill_default_server_options(ServerOptions *options)
- 		options->gss_deleg_creds = 1;
- 	if (options->gss_strict_acceptor == -1)
- 		options->gss_strict_acceptor = 1;
-+	if (options->gss_allow_s4u2self == -1)
-+		options->gss_allow_s4u2self = 0;
- 	if (options->gss_store_rekey == -1)
- 		options->gss_store_rekey = 0;
- #ifdef GSSAPI
-@@ -603,6 +608,7 @@ typedef enum {
- 	sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize,
- 	sPerSourcePenalties, sPerSourcePenaltyExemptList,
- 	sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile,
-+	sGssAllowS4U2Self, sGssProxyS4U2Services,
- 	sGssAuthentication, sGssCleanupCreds, sGssDelegateCreds, sGssEnablek5users, sGssStrictAcceptor,
- 	sGssIndicators, sGssKeyEx, sGssKexAlgorithms, sGssStoreRekey,
- 	sAcceptEnv, sSetEnv, sPermitTunnel,
-@@ -702,6 +708,8 @@ static struct {
- 	{ "gssapikexalgorithms", sGssKexAlgorithms, SSHCFG_GLOBAL },
- 	{ "gssapienablek5users", sGssEnablek5users, SSHCFG_ALL },
- 	{ "gssapiindicators", sGssIndicators, SSHCFG_ALL },
-+	{ "gssapiallows4u2self", sGssAllowS4U2Self, SSHCFG_ALL },
-+	{ "gssapiproxys4u2services", sGssProxyS4U2Services, SSHCFG_ALL },
- #else
- 	{ "gssapiauthentication", sUnsupported, SSHCFG_ALL },
- 	{ "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL },
-@@ -713,6 +721,8 @@ static struct {
- 	{ "gssapikexalgorithms", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapienablek5users", sUnsupported, SSHCFG_ALL },
- 	{ "gssapiindicators", sUnsupported, SSHCFG_ALL },
-+	{ "gssapiallows4u2self", sUnsupported, SSHCFG_ALL },
-+	{ "gssapiproxys4u2services", sUnsupported, SSHCFG_ALL },
- #endif
- 	{ "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL },
- 	{ "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL },
-@@ -1754,6 +1764,44 @@ process_server_config_line_depth(ServerOptions *options, char *line,
- 			options->gss_indicators = xstrdup(arg);
- 		break;
- 
-+	case sGssAllowS4U2Self:
-+		arg = argv_next(&ac, &av);
-+		if (!arg || *arg == '\0')
-+			fatal("%s line %d: %s missing argument.",
-+			    filename, linenum, keyword);
-+		if (strcasecmp(arg, "no") == 0)
-+			value = 0;
-+		else if (strcasecmp(arg, "yes") == 0)
-+			value = INT_MAX;
-+		else if ((value = convtime(arg)) <= 0)
-+			fatal("%s line %d: invalid %s value \"%s\".",
-+			    filename, linenum, keyword, arg);
-+		if (*activep && options->gss_allow_s4u2self == -1)
-+			options->gss_allow_s4u2self = value;
-+		break;
-+
-+	case sGssProxyS4U2Services:
-+		while ((arg = argv_next(&ac, &av)) != NULL) {
-+			if (*arg == '\0')
-+				fatal("%s line %d: %s missing argument.",
-+				    filename, linenum, keyword);
-+			if (strcasecmp(arg, "none") == 0) {
-+				/* "none" clears any previous list */
-+				for (i = 0; i < options->num_gss_proxy_services; i++)
-+					free(options->gss_proxy_services[i]);
-+				free(options->gss_proxy_services);
-+				options->gss_proxy_services = NULL;
-+				options->num_gss_proxy_services = 0;
-+				break;
-+			}
-+			if (!*activep)
-+				continue;
-+			opt_array_append(filename, linenum, keyword,
-+			    &options->gss_proxy_services,
-+			    &options->num_gss_proxy_services, arg);
-+		}
-+		break;
-+
- 	case sPasswordAuthentication:
- 		intptr = &options->password_authentication;
- 		goto parse_flag;
-@@ -3053,6 +3101,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
- 
- 	M_CP_INTOPT(password_authentication);
- 	M_CP_INTOPT(gss_authentication);
-+	M_CP_INTOPT(gss_allow_s4u2self);
- 	M_CP_INTOPT(pubkey_authentication);
- 	M_CP_INTOPT(pubkey_auth_options);
- 	M_CP_INTOPT(kerberos_authentication);
-@@ -3407,6 +3456,15 @@ dump_config(ServerOptions *o)
- 	dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey);
- 	dump_cfg_string(sGssKexAlgorithms, o->gss_kex_algorithms);
- 	dump_cfg_string(sGssIndicators, o->gss_indicators);
-+	if (o->gss_allow_s4u2self == 0)
-+		printf("%s no\n", lookup_opcode_name(sGssAllowS4U2Self));
-+	else if (o->gss_allow_s4u2self == INT_MAX)
-+		printf("%s yes\n", lookup_opcode_name(sGssAllowS4U2Self));
-+	else
-+		printf("%s %d\n", lookup_opcode_name(sGssAllowS4U2Self),
-+		    o->gss_allow_s4u2self);
-+	dump_cfg_strarray_oneline(sGssProxyS4U2Services, o->num_gss_proxy_services,
-+	    o->gss_proxy_services);
- #endif
- 	dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication);
- 	dump_cfg_fmtint(sKbdInteractiveAuthentication,
-diff --git a/servconf.h b/servconf.h
-index fea25947e..9acea32cf 100644
---- a/servconf.h
-+++ b/servconf.h
-@@ -161,6 +161,9 @@ typedef struct {
- 	int     gss_cleanup_creds;	/* If true, destroy cred cache on logout */
- 	int     gss_deleg_creds;	/* If true, accept delegated GSS credentials */
- 	int     gss_strict_acceptor;	/* If true, restrict the GSSAPI acceptor name */
-+	int     gss_allow_s4u2self; /* 0=no, INT_MAX=yes (GSS_C_INDEFINITE), >0=ticket lifetime s */
-+	char  **gss_proxy_services; /* S4U2Proxy target service principals */
-+	u_int   num_gss_proxy_services;
- 	int 	gss_store_rekey;
- 	char   *gss_kex_algorithms;	/* GSSAPI kex methods to be offered by client. */
- 	int     password_authentication;	/* If true, permit password
-@@ -314,6 +317,7 @@ TAILQ_HEAD(include_list, include_item);
- 		M_CP_STROPT(permit_user_env_allowlist); \
- 		M_CP_STROPT(pam_service_name); \
- 		M_CP_STROPT(gss_indicators); \
-+		M_CP_STRARRAYOPT(gss_proxy_services, num_gss_proxy_services, 1); \
- 		M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files, 1);\
- 		M_CP_STRARRAYOPT(revoked_keys_files, \
- 		    num_revoked_keys_files, 1); \
-diff --git a/ssh-gss.h b/ssh-gss.h
-index 1506719a9..a0d51c9be 100644
---- a/ssh-gss.h
-+++ b/ssh-gss.h
-@@ -119,6 +119,7 @@ typedef struct {
- 	int used;
- 	int updated;
- 	char **indicators; /* auth indicators */
-+	int allow_self; /* allow protocol transition */
- } ssh_gssapi_client;
- 
- typedef struct ssh_gssapi_mech_struct {
-@@ -199,6 +200,18 @@ OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t);
- void ssh_gssapi_do_child(char ***, u_int *);
- void ssh_gssapi_cleanup_creds(void);
- int ssh_gssapi_storecreds(void);
-+int  ssh_gssapi_credentials_stored(void);
-+ssh_gssapi_ccache *ssh_gssapi_get_ccache(void);
-+int  ssh_gssapi_user_has_valid_tgt(u_int);
-+int  ssh_gssapi_user_has_valid_proxy_tickets(char **, u_int, u_int);
-+int  ssh_gssapi_s4u2self(const char *, u_int);
-+void ssh_gssapi_storecreds_s4u2self(void);
-+void ssh_gssapi_s4u2proxy(char **, u_int, u_int);
-+/* Flags for ssh_gssapi_krb5_filter_ccache(): which ticket classes to remove */
-+#define SSH_GSSAPI_CCFILTER_TGT    (1u << 0) /* krbtgt/... entries */
-+#define SSH_GSSAPI_CCFILTER_SELF   (1u << 1) /* S4U2Self evidence ticket */
-+#define SSH_GSSAPI_CCFILTER_PROXY  (1u << 2) /* S4U2Proxy service tickets */
-+void ssh_gssapi_krb5_filter_ccache(u_int, char **, u_int);
- const char *ssh_gssapi_displayname(void);
- 
- char *ssh_gssapi_server_mechanisms(void);
-diff --git a/sshd-session.c b/sshd-session.c
-index 7064afd7c..d1d13d863 100644
---- a/sshd-session.c
-+++ b/sshd-session.c
-@@ -1393,6 +1393,104 @@ main(int ac, char **av)
- 		authctxt->krb5_set_env = ssh_gssapi_storecreds();
- 		restore_uid();
- 	}
-+	/*
-+	 * GSSAPIAllowS4U2Self / GSSAPIProxyS4U2Services: if no credentials were stored
-+	 * above (i.e. no GSSAPI auth with delegation occurred), use S4U2Self
-+	 * to obtain an impersonated credential for the user, then optionally
-+	 * follow with S4U2Proxy for configured target services.
-+	 *
-+	 * GSSAPIAllowS4U2Self alone:    store S4U2Self evidence ticket only;
-+	 *                            the host TGT is removed.
-+	 * GSSAPIProxyS4U2Services alone: store host TGT and S4U2Proxy service
-+	 *                            tickets; the S4U2Self evidence ticket
-+	 *                            is removed.
-+	 * Both:                     store host TGT, S4U2Self evidence ticket,
-+	 *                            and all S4U2Proxy service tickets.
-+	 *
-+	 * When S4U2Proxy tickets are present the host TGT must remain in the
-+	 * ccache; applications check for TGT presence to determine whether
-+	 * Kerberos credentials are available.  Only in GSSAPIAllowS4U2Self-alone
-+	 * mode (no proxy tickets) is the host TGT removed.
-+	 *
-+	 * Skip S4U2Self when the user already has credentials covering the
-+	 * requested lifetime: check for a valid TGT in the GSSAPIAllowS4U2Self-
-+	 * alone case, or for valid proxy tickets for every configured service
-+	 * otherwise.
-+	 */
-+	if ((options.gss_allow_s4u2self || options.num_gss_proxy_services > 0) &&
-+	    !ssh_gssapi_credentials_stored()) {
-+		u_int lifetime = (!options.gss_allow_s4u2self ||
-+		    options.gss_allow_s4u2self == INT_MAX) ?
-+		    GSS_C_INDEFINITE : (u_int)options.gss_allow_s4u2self;
-+		int skip = 0;
-+
-+		temporarily_use_uid(authctxt->pw);
-+		if (options.gss_allow_s4u2self &&
-+		    options.num_gss_proxy_services == 0) {
-+			/* S4U2Self-alone: skip if user already has a valid TGT */
-+			skip = ssh_gssapi_user_has_valid_tgt(lifetime);
-+		} else if (options.num_gss_proxy_services > 0) {
-+			/*
-+			 * Proxy-only or both: skip if every configured service
-+			 * already has a valid ticket in the user's ccache.
-+			 * Service tickets are not GSSAPI initiator credentials,
-+			 * so gss_acquire_cred() cannot be used; iterate the
-+			 * ccache with the krb5 API instead.
-+			 */
-+			skip = ssh_gssapi_user_has_valid_proxy_tickets(
-+			    options.gss_proxy_services,
-+			    options.num_gss_proxy_services,
-+			    lifetime);
-+		}
-+		restore_uid();
-+
-+		if (skip) {
-+			debug_f("user %.100s already has valid Kerberos "
-+			    "credentials, skipping S4U2Self",
-+			    authctxt->user);
-+		} else if (ssh_gssapi_s4u2self(authctxt->user, lifetime) == 0) {
-+			u_int filter;
-+
-+			temporarily_use_uid(authctxt->pw);
-+			/*
-+			 * Always create the ccache via storecreds_s4u2self so
-+			 * that s4u2proxy has a ccache to store tickets into.
-+			 * gss_krb5_copy_ccache() copies the host service's own
-+			 * TGT along with the evidence ticket; filter_ccache
-+			 * removes the ticket classes that should not be kept.
-+			 */
-+			ssh_gssapi_storecreds_s4u2self();
-+			if (options.num_gss_proxy_services > 0)
-+				ssh_gssapi_s4u2proxy(
-+				    options.gss_proxy_services,
-+				    options.num_gss_proxy_services,
-+				    lifetime);
-+
-+			/*
-+			 * Remove the host TGT only in GSSAPIAllowS4U2Self-alone
-+			 * mode; when proxy tickets are present the TGT must
-+			 * stay so that applications recognise the ccache as
-+			 * holding live Kerberos credentials.
-+			 * Remove the S4U2Self evidence ticket in proxy-only
-+			 * mode (GSSAPIProxyS4U2Services without GSSAPIAllowS4U2Self).
-+			 */
-+			filter = 0;
-+			if (options.gss_allow_s4u2self &&
-+			    options.num_gss_proxy_services == 0)
-+				filter = SSH_GSSAPI_CCFILTER_TGT |
-+				    SSH_GSSAPI_CCFILTER_PROXY;
-+			else if (!options.gss_allow_s4u2self)
-+				filter = SSH_GSSAPI_CCFILTER_SELF;
-+			if (filter != 0)
-+				ssh_gssapi_krb5_filter_ccache(filter,
-+				    options.gss_proxy_services,
-+				    options.num_gss_proxy_services);
-+			restore_uid();
-+		} else {
-+			logit("S4U2Self failed for user %.100s, continuing",
-+			    authctxt->user);
-+		}
-+	}
- #endif
- #ifdef WITH_SELINUX
- 	sshd_selinux_setup_exec_context(authctxt->pw->pw_name,
-diff --git a/sshd_config.5 b/sshd_config.5
-index c28220077..30d1d59da 100644
---- a/sshd_config.5
-+++ b/sshd_config.5
-@@ -846,6 +846,76 @@ FIDO2-based pre-authentication in FreeIPA, using FIDO2 USB and NFC tokens
- The default
- .Dq none
- is to not use GSSAPI authentication indicators for access decisions.
-+.It Cm GSSAPIAllowS4U2Self
-+Controls whether the SSH server performs a Kerberos protocol transition
-+(S4U2Self) after a successful authentication using any other method.
-+Accepted values are
-+.Cm no ,
-+.Cm yes ,
-+or a time interval (see
-+.Sx TIME FORMATS
-+below).
-+.Cm no
-+disables S4U2Self entirely.
-+.Cm yes
-+enables S4U2Self and requests a ticket with the maximum lifetime
-+permitted by the KDC.
-+A time interval (e.g.\&
-+.Cm 8h ,
-+.Cm 1d )
-+enables S4U2Self and requests a ticket valid for at most that duration.
-+The option is a no-op when delegated GSSAPI credentials are already available.
-+The obtained service ticket is stored in the default credentials cache and is
-+accessible to any application that has access to the Kerberos host principal
-+.Pq host/machine.fqdn@REALM
-+credentials on the same host.
-+.Pp
-+The default is
-+.Cm no .
-+.It Cm GSSAPIProxyS4U2Services
-+Specifies a list of Kerberos service principals for which constrained
-+delegation (S4U2Proxy) tickets should be obtained after a successful
-+S4U2Self protocol transition.
-+Each entry must be a fully-qualified Kerberos principal name of the form
-+.Ar service/host@REALM .
-+Multiple principals may be listed, separated by whitespace.
-+The keyword
-+.Cm none
-+clears any previously set list.
-+.Pp
-+This option may be used independently of
-+.Cm GSSAPIAllowS4U2Self .
-+When S4U2Self succeeds, the server iterates the list and calls
-+.Xr gss_init_sec_context 3
-+for each principal with the proxy credential obtained by S4U2Self as
-+the initiator.
-+The GSSAPI library presents the evidence ticket to the KDC via an
-+S4U2Proxy TGS-REQ; if the host service holds the necessary constrained-
-+delegation permission in the KDC, a service ticket from the user to
-+the target service is issued.
-+These tickets are accumulated and then flushed into the user's ccache
-+via
-+.Xr gss_store_cred 3 ,
-+so that any application running in the user's session can use them
-+without further interaction.
-+The AP-REQ output token of each
-+.Xr gss_init_sec_context 3
-+call is discarded; no network connection to the target service is made.
-+.Pp
-+When used together with
-+.Cm GSSAPIAllowS4U2Self ,
-+the TGT and S4U2Self ticket are also stored in the user's ccache in
-+addition to the S4U2Proxy service tickets.
-+When used alone (without
-+.Cm GSSAPIAllowS4U2Self ) ,
-+only the S4U2Proxy service tickets are stored; the intermediate S4U2Self
-+credential is not placed in the user's ccache.
-+.Pp
-+This option supports
-+.Cm Match
-+blocks, allowing per-user or per-host lists of delegation targets.
-+.Pp
-+The default is empty (no S4U2Proxy delegation is performed).
- .It Cm HostbasedAcceptedAlgorithms
- The default is handled system-wide by
- .Xr crypto-policies 7 .
--- 
-2.53.0
-

diff --git a/0052-openssh-10.2p1-pkcs11-uri.patch b/0052-openssh-10.2p1-pkcs11-uri.patch
new file mode 100644
index 0000000..4408fe9
--- /dev/null
+++ b/0052-openssh-10.2p1-pkcs11-uri.patch
@@ -0,0 +1,3423 @@
+From bb9fdcfebdd652ea819b5fbf3316794e9a7ee9e2 Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Mon, 15 Dec 2025 14:24:07 +0100
+Subject: [PATCH 52/54] openssh-10.2p1-pkcs11-uri
+
+---
+ .depend                          |   1 +
+ Makefile.in                      |  26 +-
+ configure.ac                     |  37 ++
+ regress/Makefile                 |   5 +-
+ regress/pkcs11-uri.sh            | 410 ++++++++++++++++
+ regress/unittests/Makefile       |   2 +-
+ regress/unittests/pkcs11/tests.c | 353 ++++++++++++++
+ ssh-add.c                        |  48 +-
+ ssh-agent.c                      | 117 ++++-
+ ssh-keygen.c                     |   7 +-
+ ssh-pkcs11-client.c              |  19 +
+ ssh-pkcs11-uri.c                 | 438 ++++++++++++++++++
+ ssh-pkcs11-uri.h                 |  46 ++
+ ssh-pkcs11.c                     | 771 ++++++++++++++++++++++---------
+ ssh-pkcs11.h                     |   4 +
+ ssh.c                            | 104 ++++-
+ ssh_config.5                     |  15 +
+ sshkey.c                         |   4 +
+ 18 files changed, 2134 insertions(+), 273 deletions(-)
+ create mode 100644 regress/pkcs11-uri.sh
+ create mode 100644 regress/unittests/pkcs11/tests.c
+ create mode 100644 ssh-pkcs11-uri.c
+ create mode 100644 ssh-pkcs11-uri.h
+
+diff --git a/.depend b/.depend
+index 7e5f3dcd3..9037c6ef6 100644
+--- a/.depend
++++ b/.depend
+@@ -144,6 +144,7 @@ ssh-mldsa-eddsa.o: includes.h config.h defines.h platform.h openbsd-compat/openb
+ ssh-pkcs11-client.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/bsd-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 pathnames.h xmalloc.h sshbuf.h log.h ssherr.h misc.h sshkey.h authfd.h atomicio.h ssh-pkcs11.h
+ ssh-pkcs11-helper.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/bsd-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 sshbuf.h log.h ssherr.h misc.h sshkey.h authfd.h ssh-pkcs11.h
+ ssh-pkcs11.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/bsd-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 log.h ssherr.h sshkey.h ssh-pkcs11.h
++ssh-pkcs11-uri.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/bsd-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-rsa.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/bsd-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-sk-client.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/bsd-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 log.h ssherr.h sshbuf.h sshkey.h msg.h pathnames.h ssh-sk.h misc.h
+ ssh-sk-helper.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/bsd-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 log.h ssherr.h sshkey.h authfd.h misc.h sshbuf.h msg.h uidswap.h ssh-sk.h ssh-pkcs11.h
+diff --git a/Makefile.in b/Makefile.in
+index 56c4c88cf..23cf085a5 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -113,12 +113,12 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
+ 	sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
+ 	sshbuf-io.o misc-agent.o ssherr-libcrypto.o auditstub.o
+ 
+-P11OBJS= ssh-pkcs11-client.o
++P11OBJS= ssh-pkcs11-client.o ssh-pkcs11-uri.o
+ 
+ SKOBJS=	ssh-sk-client.o
+ 
+ SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
+-	sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o $(SKOBJS)
++	sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
+ 
+ SSHDOBJS=sshd.o \
+ 	platform-listen.o \
+@@ -162,11 +162,11 @@ SSHADD_OBJS=	ssh-add.o $(P11OBJS) $(SKOBJS)
+ 
+ SSHAGENT_OBJS=	ssh-agent.o $(P11OBJS) $(SKOBJS)
+ 
+-SSHKEYGEN_OBJS=	ssh-keygen.o sshsig.o ssh-pkcs11.o $(SKOBJS)
++SSHKEYGEN_OBJS=	ssh-keygen.o sshsig.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
+ 
+ SSHKEYSIGN_OBJS=ssh-keysign.o readconf.o uidswap.o $(P11OBJS) $(SKOBJS)
+ 
+-P11HELPER_OBJS=	ssh-pkcs11-helper.o ssh-pkcs11.o $(SKOBJS)
++P11HELPER_OBJS=	ssh-pkcs11-helper.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
+ 
+ SKHELPER_OBJS=	ssh-sk-helper.o ssh-sk.o sk-usbhid.o ssherr-nolibcrypto.o
+ 
+@@ -336,6 +336,8 @@ clean:	regressclean
+ 	rm -f regress/unittests/sshsig/test_sshsig$(EXEEXT)
+ 	rm -f regress/unittests/utf8/*.o
+ 	rm -f regress/unittests/utf8/test_utf8$(EXEEXT)
++	rm -f regress/unittests/pkcs11/*.o
++	rm -f regress/unittests/pkcs11/test_pkcs11$(EXEEXT)
+ 	rm -f regress/misc/sk-dummy/*.o
+ 	rm -f regress/misc/sk-dummy/*.lo
+ 	rm -f regress/misc/ssh-verify-attestation/ssh-verify-attestation$(EXEEXT)
+@@ -379,6 +381,8 @@ distclean:	regressclean
+ 	rm -f regress/unittests/sshsig/test_sshsig
+ 	rm -f regress/unittests/utf8/*.o
+ 	rm -f regress/unittests/utf8/test_utf8
++	rm -f regress/unittests/pkcs11/*.o
++	rm -f regress/unittests/pkcs11/test_pkcs11
+ 	rm -f regress/misc/sk-dummy/*.o
+ 	rm -f regress/misc/sk-dummy/*.lo
+ 	rm -f regress/misc/sk-dummy/sk-dummy.so
+@@ -561,6 +565,7 @@ regress-prep:
+ 	$(MKDIR_P) `pwd`/regress/unittests/sshkey
+ 	$(MKDIR_P) `pwd`/regress/unittests/sshsig
+ 	$(MKDIR_P) `pwd`/regress/unittests/utf8
++	$(MKDIR_P) `pwd`/regress/unittests/pkcs11
+ 	$(MKDIR_P) `pwd`/regress/misc/sk-dummy
+ 	$(MKDIR_P) `pwd`/regress/misc/ssh-verify-attestation
+ 	[ -f `pwd`/regress/Makefile ] || \
+@@ -764,6 +769,16 @@ regress/unittests/utf8/test_utf8$(EXEEXT): \
+ 	    regress/unittests/test_helper/libtest_helper.a \
+ 	    -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(TESTLIBS)
+ 
++UNITTESTS_TEST_PKCS11_OBJS=\
++	regress/unittests/pkcs11/tests.o
++
++regress/unittests/pkcs11/test_pkcs11$(EXEEXT): \
++    ${UNITTESTS_TEST_PKCS11_OBJS} ssh-pkcs11-uri.o \
++    regress/unittests/test_helper/libtest_helper.a libssh.a
++	$(LD) -o $@ $(LDFLAGS) $(UNITTESTS_TEST_PKCS11_OBJS) \
++	    regress/unittests/test_helper/libtest_helper.a \
++	    ssh-pkcs11-uri.o -lssh -lopenbsd-compat -lcrypto $(LIBS) -lm
++
+ # These all need to be compiled -fPIC, so they are treated differently.
+ SK_DUMMY_OBJS=\
+ 	regress/misc/sk-dummy/sk-dummy.lo \
+@@ -811,7 +826,8 @@ regress-unit-binaries: regress-prep $(REGRESSLIBS) \
+ 	regress/unittests/sshbuf/test_sshbuf$(EXEEXT) \
+ 	regress/unittests/sshkey/test_sshkey$(EXEEXT) \
+ 	regress/unittests/sshsig/test_sshsig$(EXEEXT) \
+-	regress/unittests/utf8/test_utf8$(EXEEXT)
++	regress/unittests/utf8/test_utf8$(EXEEXT) \
++	regress/unittests/pkcs11/test_pkcs11$(EXEEXT) \
+ 
+ tests:	file-tests t-exec interop-tests extra-tests unit
+ 	echo all tests passed
+diff --git a/configure.ac b/configure.ac
+index 918ccd5df..8e679f770 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -2305,12 +2305,14 @@ AC_LINK_IFELSE(
+ 	[AC_DEFINE([HAVE_ISBLANK], [1], [Define if you have isblank(3C).])
+ ])
+ 
++SCARD_MSG="yes"
+ disable_pkcs11=
+ AC_ARG_ENABLE([pkcs11],
+ 	[  --disable-pkcs11        disable PKCS#11 support code [no]],
+ 	[
+ 		if test "x$enableval" = "xno" ; then
+ 			disable_pkcs11=1
++			SCARD_MSG="no"
+ 		fi
+ 	]
+ )
+@@ -2340,6 +2342,40 @@ AC_SEARCH_LIBS([dlopen], [dl])
+ AC_CHECK_FUNCS([dlopen])
+ AC_CHECK_DECL([RTLD_NOW], [], [], [#include <dlfcn.h>])
+ 
++# Check whether we have a p11-kit, we got default provider on command line
++DEFAULT_PKCS11_PROVIDER_MSG="no"
++AC_ARG_WITH([default-pkcs11-provider],
++	[  --with-default-pkcs11-provider[[=PATH]]   Use default pkcs11 provider (p11-kit detected by default)],
++	[ if test "x$withval" != "xno" && test "x$disable_pkcs11" = "x"; then
++		if test "x$withval" = "xyes" ; then
++			AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
++			if test "x$PKGCONFIG" != "xno"; then
++				AC_MSG_CHECKING([if $PKGCONFIG knows about p11-kit])
++				if "$PKGCONFIG" "p11-kit-1"; then
++					AC_MSG_RESULT([yes])
++					use_pkgconfig_for_p11kit=yes
++				else
++					AC_MSG_RESULT([no])
++				fi
++			fi
++		else
++			PKCS11_PATH="${withval}"
++		fi
++		if test "x$use_pkgconfig_for_p11kit" = "xyes"; then
++			PKCS11_PATH=`$PKGCONFIG --variable=proxy_module p11-kit-1`
++		fi
++		AC_CHECK_FILE("$PKCS11_PATH",
++			[ AC_DEFINE_UNQUOTED([PKCS11_DEFAULT_PROVIDER], ["$PKCS11_PATH"], [Path to default PKCS#11 provider (p11-kit proxy)])
++			  DEFAULT_PKCS11_PROVIDER_MSG="$PKCS11_PATH"
++			],
++			[ AC_MSG_ERROR([Requested PKCS11 provided not found]) ]
++		)
++	else
++		AC_MSG_WARN([Needs PKCS11 support to enable default pkcs11 provider])
++	fi ]
++)
++
++
+ # IRIX has a const char return value for gai_strerror()
+ AC_CHECK_FUNCS([gai_strerror], [
+ 	AC_DEFINE([HAVE_GAI_STRERROR])
+@@ -5982,6 +6018,7 @@ echo "                  BSD Auth support: $BSD_AUTH_MSG"
+ echo "              Random number source: $RAND_MSG"
+ echo "             Privsep sandbox style: $SANDBOX_STYLE"
+ echo "                   PKCS#11 support: $enable_pkcs11"
++echo "          Default PKCS#11 provider: $DEFAULT_PKCS11_PROVIDER_MSG"
+ echo "                  U2F/FIDO support: $enable_sk"
+ 
+ echo ""
+diff --git a/regress/Makefile b/regress/Makefile
+index a6f81bef4..98a653256 100644
+--- a/regress/Makefile
++++ b/regress/Makefile
+@@ -118,6 +118,7 @@ LTESTS= 	connect \
+ 		penalty-expire \
+ 		connect-bigconf \
+ 		ssh-pkcs11 \
++		pkcs11-uri \
+ 		ssh-tty \
+ 		proxyjump
+ 
+@@ -144,7 +145,8 @@ CLEANFILES=	*.core actual agent-key.* authorized_keys_${USERNAME} \
+ 		known_hosts known_hosts-cert known_hosts.* krl-* ls.copy \
+ 		modpipe netcat no_identity_config \
+ 		pidfile putty.rsa2 ready regress.log remote_pid \
+-		revoked-* rsa rsa-agent rsa-agent.pub rsa.pub rsa_ssh2_cr.prv \
++		revoked-* rsa rsa-agent rsa-agent.pub rsa-agent-cert.pub \
++		rsa.pub rsa_ssh2_cr.prv pkcs11*.crt pkcs11*.key pkcs11.info \
+ 		rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \
+ 		scp-ssh-wrapper.scp setuid-allowed sftp-server.log \
+ 		sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \
+@@ -294,6 +296,7 @@ unit unit-bench:
+ 		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/pkcs11/test_pkcs11 ; \
+ 		 $$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf $${ARGS}; \
+ 		 $$V ${.OBJDIR}/unittests/sshkey/test_sshkey \
+ 			-d ${.CURDIR}/unittests/sshkey/testdata $${ARGS}; \
+diff --git a/regress/pkcs11-uri.sh b/regress/pkcs11-uri.sh
+new file mode 100644
+index 000000000..72d69bda0
+--- /dev/null
++++ b/regress/pkcs11-uri.sh
+@@ -0,0 +1,410 @@
++#
++#  Copyright (c) 2017 Red Hat
++#
++#  Authors: Jakub Jelen <jjelen@redhat.com>
++#
++#  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.
++
++tid="pkcs11 tests with soft token"
++
++try_token_libs() {
++	for _lib in "$@" ; do
++		if test -f "$_lib" ; then
++			verbose "Using token library $_lib"
++			TEST_SSH_PKCS11="$_lib"
++			return
++		fi
++	done
++	echo "skipped: Unable to find PKCS#11 token library"
++	exit 0
++}
++
++try_token_libs \
++	/usr/local/lib/softhsm/libsofthsm2.so \
++	/usr/lib64/pkcs11/libsofthsm2.so \
++	/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so
++
++TEST_SSH_PIN=1234
++TEST_SSH_SOPIN=12345678
++if [ "x$TEST_SSH_SSHPKCS11HELPER" != "x" ]; then
++	SSH_PKCS11_HELPER="${TEST_SSH_SSHPKCS11HELPER}"
++	export SSH_PKCS11_HELPER
++fi
++
++test -f "$TEST_SSH_PKCS11" || fatal "$TEST_SSH_PKCS11 does not exist"
++
++# setup environment for softhsm token
++DIR=$OBJ/SOFTHSM
++rm -rf $DIR
++TOKEN=$DIR/tokendir
++mkdir -p $TOKEN
++SOFTHSM2_CONF=$DIR/softhsm2.conf
++export SOFTHSM2_CONF
++cat > $SOFTHSM2_CONF << EOF
++# SoftHSM v2 configuration file
++directories.tokendir = ${TOKEN}
++objectstore.backend = file
++# ERROR, WARNING, INFO, DEBUG
++log.level = DEBUG
++# If CKF_REMOVABLE_DEVICE flag should be set
++slots.removable = false
++EOF
++out=$(softhsm2-util --init-token --free --label token-slot-0 --pin "$TEST_SSH_PIN" --so-pin "$TEST_SSH_SOPIN")
++slot=$(echo -- $out | sed 's/.* //')
++
++# prevent ssh-agent from calling ssh-askpass
++SSH_ASKPASS=/usr/bin/true
++export SSH_ASKPASS
++unset DISPLAY
++# We need interactive access to test PKCS# since it prompts for PIN
++# Backup ssh_proxy before modifications
++cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak
++sed -i 's/.*BatchMode.*//g' $OBJ/ssh_proxy
++# Remove IdentityFile entries to prevent exceeding MaxAuthTries when using
++# PKCS11Provider (-I) or PKCS11 URIs. The default ssh_config includes multiple
++# identity files that would be tried before PKCS11 keys, causing authentication
++# to fail with "Too many authentication failures" before PKCS11 keys are reached.
++grep -iv IdentityFile $OBJ/ssh_proxy > $OBJ/ssh_proxy.tmp
++mv $OBJ/ssh_proxy.tmp $OBJ/ssh_proxy
++
++# start command w/o tty, so ssh accepts pin from stdin (from agent-pkcs11.sh)
++notty() {
++	perl -e 'use POSIX; POSIX::setsid();
++	    if (fork) { wait; exit($? >> 8); } else { exec(@ARGV) }' "$@"
++}
++
++trace "generating keys"
++ID1="02"
++ID2="04"
++ID3="06"
++RSA=${DIR}/RSA
++EC=${DIR}/EC
++ED25519=${DIR}/ED25519
++openssl genpkey -algorithm rsa > $RSA
++openssl pkcs8 -nocrypt -in $RSA |\
++    softhsm2-util --slot "$slot" --label "SSH RSA Key $ID1" --id $ID1 \
++	--pin "$TEST_SSH_PIN" --import /dev/stdin
++openssl genpkey \
++    -genparam \
++    -algorithm ec \
++    -pkeyopt ec_paramgen_curve:prime256v1 |\
++    openssl genpkey \
++    -paramfile /dev/stdin > $EC
++openssl pkcs8 -nocrypt -in $EC |\
++    softhsm2-util --slot "$slot" --label "SSH ECDSA Key $ID2" --id $ID2 \
++	--pin "$TEST_SSH_PIN" --import /dev/stdin
++openssl genpkey -algorithm ed25519 > $ED25519
++openssl pkcs8 -nocrypt -in $ED25519 |\
++    softhsm2-util --slot "$slot" --label "SSH ED25519 Key $ID3" --id $ID3 \
++	--pin "$TEST_SSH_PIN" --import /dev/stdin
++
++trace "List the keys in the ssh-keygen with PKCS#11 URIs"
++${SSHKEYGEN} -D ${TEST_SSH_PKCS11} > $OBJ/token_keys
++if [ $? -ne 0 ]; then
++	fail "FAIL: keygen fails to enumerate keys on PKCS#11 token"
++fi
++grep "pkcs11:" $OBJ/token_keys > /dev/null
++if [ $? -ne 0 ]; then
++	fail "FAIL: The keys from ssh-keygen do not contain PKCS#11 URI as a comment"
++fi
++
++# Set the ECDSA key to authorized keys
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "Simple connect with ssh (without PKCS#11 URI)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -I ${TEST_SSH_PKCS11} \
++    -F $OBJ/ssh_proxy somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with pkcs11 failed (exit code $r)"
++fi
++
++trace "Connect with PKCS#11 URI"
++trace "  (ECDSA key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++    -i "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
++fi
++
++trace "  (RSA key should fail)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++     -i "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
++fi
++
++# Set the ED25519 key as authorized
++grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "  (ED25519 key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++     -i "pkcs11:id=%${ID3}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
++fi
++
++# Set the ECDSA key back as authorized
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "Connect with PKCS#11 URI including PIN should not prompt"
++trace "  (ECDSA key should succeed)"
++${SSH} -F $OBJ/ssh_proxy -i \
++    "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
++fi
++
++trace "  (RSA key should fail)"
++${SSH} -F $OBJ/ssh_proxy -i \
++    "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
++fi
++
++# Set the ED25519 key as authorized
++grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "  (ED25519 key should succeed)"
++${SSH} -F $OBJ/ssh_proxy -i \
++    "pkcs11:id=%${ID3}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
++fi
++
++# Set the ECDSA key back as authorized
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "Connect with various filtering options in PKCS#11 URI"
++trace "  (by object label, ECDSA should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++    -i "pkcs11:object=SSH%20ECDSA%20Key%2004?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
++fi
++
++trace "  (by object label, RSA key should fail)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++     -i "pkcs11:object=SSH%20RSA%20Key%2002?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
++fi
++
++# Set the ED25519 key as authorized
++grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "  (by object label, ED25519 key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++     -i "pkcs11:object=SSH%20ED25519%20Key%2006?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
++fi
++
++# Set the ECDSA key back as authorized
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "  (by token label, ECDSA key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++    -i "pkcs11:id=%${ID2};token=token-slot-0?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
++fi
++
++# Set the ED25519 key as authorized
++grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "  (by token label, ED25519 key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++    -i "pkcs11:id=%${ID3};token=token-slot-0?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
++fi
++
++# Set the ECDSA key back as authorized
++grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "  (by wrong token label, should fail)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++     -i "pkcs11:token=token-slot-99?module-path=${TEST_SSH_PKCS11}" somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
++fi
++
++
++
++
++trace "Test PKCS#11 URI specification in configuration files"
++echo "IdentityFile \"pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}\"" \
++    >> $OBJ/ssh_proxy
++trace "  (ECDSA key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI in config failed (exit code $r)"
++fi
++
++# Set the RSA key as authorized
++grep "RSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
++
++trace "  (RSA key should fail)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
++r=$?
++if [ $r -eq 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI in config succeeded (should fail)"
++fi
++sed -i -e "/IdentityFile/d" $OBJ/ssh_proxy
++
++trace "Combination of PKCS11Provider and PKCS11URI on commandline"
++trace "  (RSA key should succeed)"
++echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
++    -i "pkcs11:id=%${ID1}" -I ${TEST_SSH_PKCS11} somehost exit 5
++r=$?
++if [ $r -ne 5 ]; then
++	fail "FAIL: ssh connect with PKCS#11 URI and provider combination" \
++	    "failed (exit code $r)"
++fi
++
++trace "Regress: Missing provider in PKCS11URI option"
++${SSH} -F $OBJ/ssh_proxy \
++    -o IdentityFile=\"pkcs11:token=segfault\" somehost exit 5
++r=$?
++if [ $r -eq 139 ]; then
++	fail "FAIL: ssh connect with missing provider_id from configuration option" \
++	    "crashed (exit code $r)"
++fi
++
++
++trace "SSH Agent can work with PKCS#11 URI"
++trace "start the agent"
++eval `${SSHAGENT} -s` >  /dev/null
++
++r=$?
++if [ $r -ne 0 ]; then
++	fail "could not start ssh-agent: exit code $r"
++else
++	trace "add whole provider to agent"
++	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
++	    "pkcs11:?module-path=${TEST_SSH_PKCS11}" #> /dev/null 2>&1
++	r=$?
++	if [ $r -ne 0 ]; then
++		fail "FAIL: ssh-add failed with whole provider: exit code $r"
++	fi
++
++	trace " pkcs11 list via agent (all keys)"
++	${SSHADD} -l > /dev/null 2>&1
++	r=$?
++	if [ $r -ne 0 ]; then
++		fail "FAIL: ssh-add -l failed with whole provider: exit code $r"
++	fi
++
++	trace " pkcs11 connect via agent (all keys)"
++	${SSH} -F $OBJ/ssh_proxy somehost exit 5
++	r=$?
++	if [ $r -ne 5 ]; then
++		fail "FAIL: ssh connect failed with whole provider (exit code $r)"
++	fi
++
++	trace " remove pkcs11 keys (all keys)"
++	${SSHADD} -d "pkcs11:?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
++	r=$?
++	if [ $r -ne 0 ]; then
++		fail "FAIL: ssh-add -d failed with whole provider: exit code $r"
++	fi
++
++	trace "add only RSA key to the agent"
++	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
++	    "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
++	r=$?
++	if [ $r -ne 0 ]; then
++		fail "FAIL ssh-add failed with RSA key: exit code $r"
++	fi
++
++	trace " pkcs11 connect via agent (RSA key)"
++	${SSH} -F $OBJ/ssh_proxy somehost exit 5
++	r=$?
++	if [ $r -ne 5 ]; then
++		fail "FAIL: ssh connect failed with RSA key (exit code $r)"
++	fi
++
++	trace " remove RSA pkcs11 key"
++	${SSHADD} -d "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" \
++	    > /dev/null 2>&1
++	r=$?
++	if [ $r -ne 0 ]; then
++		fail "FAIL: ssh-add -d failed with RSA key: exit code $r"
++	fi
++
++	trace "add only ECDSA key to the agent"
++	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
++	    "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
++	r=$?
++	if [ $r -ne 0 ]; then
++		fail "FAIL: ssh-add failed with second key: exit code $r"
++	fi
++
++	trace " pkcs11 connect via agent (ECDSA key should fail)"
++	${SSH} -F $OBJ/ssh_proxy somehost exit 5
++	r=$?
++	if [ $r -eq 5 ]; then
++		fail "FAIL: ssh connect passed with ECDSA key (should fail)"
++	fi
++
++	trace "add also the RSA key to the agent"
++	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
++	    "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
++	r=$?
++	if [ $r -ne 0 ]; then
++		fail "FAIL: ssh-add failed with first key: exit code $r"
++	fi
++
++	trace " remove ECDSA pkcs11 key"
++	${SSHADD} -d "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" \
++	    > /dev/null 2>&1
++	r=$?
++	if [ $r -ne 0 ]; then
++		fail "ssh-add -d failed with ECDSA key: exit code $r"
++	fi
++
++	trace " remove already-removed pkcs11 key should fail"
++	${SSHADD} -d "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" \
++	    > /dev/null 2>&1
++	r=$?
++	if [ $r -eq 0 ]; then
++		fail "FAIL: ssh-add -d passed with non-existing key (should fail)"
++	fi
++
++	trace " pkcs11 connect via agent (the RSA key should be still usable)"
++	${SSH} -F $OBJ/ssh_proxy somehost exit 5
++	r=$?
++	if [ $r -ne 5 ]; then
++		fail "ssh connect failed with RSA key (after removing ECDSA): exit code $r"
++	fi
++
++	trace "kill agent"
++	${SSHAGENT} -k > /dev/null
++fi
++
++# Restore original ssh_proxy
++mv $OBJ/ssh_proxy_bak $OBJ/ssh_proxy
+diff --git a/regress/unittests/Makefile b/regress/unittests/Makefile
+index 5d482eab4..51bed5baf 100644
+--- a/regress/unittests/Makefile
++++ b/regress/unittests/Makefile
+@@ -1,6 +1,6 @@
+ #	$OpenBSD: Makefile,v 1.15 2026/06/14 04:08:05 djm Exp $
+ 
+ SUBDIR=	test_helper sshbuf sshkey bitmap kex hostkeys utf8 match conversion
+-SUBDIR+=authopt misc sshsig servconf crypto
++SUBDIR+=authopt misc sshsig servconf crypto pkcs11
+ 
+ .include <bsd.subdir.mk>
+diff --git a/regress/unittests/pkcs11/tests.c b/regress/unittests/pkcs11/tests.c
+new file mode 100644
+index 000000000..89ba45c4e
+--- /dev/null
++++ b/regress/unittests/pkcs11/tests.c
+@@ -0,0 +1,353 @@
++/*
++ * Copyright (c) 2017 Red Hat
++ *
++ * Authors: Jakub Jelen <jjelen@redhat.com>
++ *
++ * 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 "includes.h"
++
++#include <locale.h>
++#include <string.h>
++
++#include "../test_helper/test_helper.h"
++
++#include "sshbuf.h"
++#include "ssh-pkcs11-uri.h"
++
++#define EMPTY_URI compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
++
++/* prototypes are not public -- specify them here internally for tests */
++struct sshbuf *percent_encode(const char *, size_t, char *);
++int percent_decode(char *, char **);
++
++void
++compare_uri(struct pkcs11_uri *a, struct pkcs11_uri *b)
++{
++	ASSERT_PTR_NE(a, NULL);
++	ASSERT_PTR_NE(b, NULL);
++	ASSERT_SIZE_T_EQ(a->id_len, b->id_len);
++	ASSERT_MEM_EQ(a->id, b->id, a->id_len);
++	if (b->object != NULL)
++		ASSERT_STRING_EQ(a->object, b->object);
++	else /* both should be null */
++		ASSERT_PTR_EQ(a->object, b->object);
++	if (b->module_path != NULL)
++		ASSERT_STRING_EQ(a->module_path, b->module_path);
++	else /* both should be null */
++		ASSERT_PTR_EQ(a->module_path, b->module_path);
++	if (b->token != NULL)
++		ASSERT_STRING_EQ(a->token, b->token);
++	else /* both should be null */
++		ASSERT_PTR_EQ(a->token, b->token);
++	if (b->manuf != NULL)
++		ASSERT_STRING_EQ(a->manuf, b->manuf);
++	else /* both should be null */
++		ASSERT_PTR_EQ(a->manuf, b->manuf);
++	if (b->lib_manuf != NULL)
++		ASSERT_STRING_EQ(a->lib_manuf, b->lib_manuf);
++	else /* both should be null */
++		ASSERT_PTR_EQ(a->lib_manuf, b->lib_manuf);
++	if (b->serial != NULL)
++		ASSERT_STRING_EQ(a->serial, b->serial);
++	else /* both should be null */
++		ASSERT_PTR_EQ(a->serial, b->serial);
++}
++
++void
++check_parse_rv(char *uri, struct pkcs11_uri *expect, int expect_rv)
++{
++	char *buf = NULL, *str;
++	struct pkcs11_uri *pkcs11uri = NULL;
++	int rv;
++
++	if (expect_rv == 0)
++		str = "Valid";
++	else
++		str = "Invalid";
++	asprintf(&buf, "%s PKCS#11 URI parsing: %s", str, uri);
++	TEST_START(buf);
++	free(buf);
++	pkcs11uri = pkcs11_uri_init();
++	rv = pkcs11_uri_parse(uri, pkcs11uri);
++	ASSERT_INT_EQ(rv, expect_rv);
++	if (rv == 0) /* in case of failure result is undefined */
++		compare_uri(pkcs11uri, expect);
++	pkcs11_uri_cleanup(pkcs11uri);
++	free(expect);
++	TEST_DONE();
++}
++
++void
++check_parse(char *uri, struct pkcs11_uri *expect)
++{
++	check_parse_rv(uri, expect, 0);
++}
++
++struct pkcs11_uri *
++compose_uri(unsigned char *id, size_t id_len, char *token, char *lib_manuf,
++    char *manuf, char *serial, char *module_path, char *object, char *pin)
++{
++	struct pkcs11_uri *uri = pkcs11_uri_init();
++	if (id_len > 0) {
++		uri->id_len = id_len;
++		uri->id = id;
++	}
++	uri->module_path = module_path;
++	uri->token = token;
++	uri->lib_manuf = lib_manuf;
++	uri->manuf = manuf;
++	uri->serial = serial;
++	uri->object = object;
++	uri->pin = pin;
++	return uri;
++}
++
++static void
++test_parse_valid(void)
++{
++	/* path arguments */
++	check_parse("pkcs11:id=%01",
++	    compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
++	check_parse("pkcs11:id=%00%01",
++	    compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
++	check_parse("pkcs11:token=SSH%20Keys",
++	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
++	check_parse("pkcs11:library-manufacturer=OpenSC",
++	    compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL, NULL));
++	check_parse("pkcs11:manufacturer=piv_II",
++	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL, NULL));
++	check_parse("pkcs11:serial=IamSerial",
++	    compose_uri(NULL, 0, NULL, NULL, NULL, "IamSerial", NULL, NULL, NULL));
++	check_parse("pkcs11:object=SIGN%20Key",
++	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, "SIGN Key", NULL));
++	/* query arguments */
++	check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
++	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
++	check_parse("pkcs11:?pin-value=123456",
++	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, "123456"));
++
++	/* combinations */
++	/* ID SHOULD be percent encoded */
++	check_parse("pkcs11:token=SSH%20Key;id=0",
++	    compose_uri("0", 1, "SSH Key", NULL, NULL, NULL, NULL, NULL, NULL));
++	check_parse(
++	    "pkcs11:manufacturer=CAC?module-path=/usr/lib64/p11-kit-proxy.so",
++	    compose_uri(NULL, 0, NULL, NULL, "CAC", NULL,
++	    "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
++	check_parse(
++	    "pkcs11:object=RSA%20Key?module-path=/usr/lib64/pkcs11/opencryptoki.so",
++	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL,
++	    "/usr/lib64/pkcs11/opencryptoki.so", "RSA Key", NULL));
++	check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so&pin-value=123456",
++	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, "123456"));
++
++	/* empty path component matches everything */
++	check_parse("pkcs11:", EMPTY_URI);
++
++	/* empty string is a valid to match against (and different from NULL) */
++	check_parse("pkcs11:token=",
++	    compose_uri(NULL, 0, "", NULL, NULL, NULL, NULL, NULL, NULL));
++	/* Percent character needs to be percent-encoded */
++	check_parse("pkcs11:token=%25",
++	     compose_uri(NULL, 0, "%", NULL, NULL, NULL, NULL, NULL, NULL));
++}
++
++static void
++test_parse_invalid(void)
++{
++	/* Invalid percent encoding */
++	check_parse_rv("pkcs11:id=%0", EMPTY_URI, -1);
++	/* Invalid percent encoding */
++	check_parse_rv("pkcs11:id=%ZZ", EMPTY_URI, -1);
++	/* Space MUST be percent encoded -- XXX not enforced yet */
++	check_parse("pkcs11:token=SSH Keys",
++	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
++	/* MUST NOT contain duplicate attributes of the same name */
++	check_parse_rv("pkcs11:id=%01;id=%02", EMPTY_URI, -1);
++	/* MUST NOT contain duplicate attributes of the same name */
++	check_parse_rv("pkcs11:?pin-value=111111&pin-value=123456", EMPTY_URI, -1);
++	/* Unrecognized attribute in path are ignored with log message */
++	check_parse("pkcs11:key_name=SSH", EMPTY_URI);
++	/* Unrecognized attribute in query SHOULD be ignored */
++	check_parse("pkcs11:?key_name=SSH", EMPTY_URI);
++}
++
++void
++check_gen(char *expect, struct pkcs11_uri *uri)
++{
++	char *buf = NULL, *uri_str;
++
++	asprintf(&buf, "Valid PKCS#11 URI generation: %s", expect);
++	TEST_START(buf);
++	free(buf);
++	uri_str = pkcs11_uri_get(uri);
++	ASSERT_PTR_NE(uri_str, NULL);
++	ASSERT_STRING_EQ(uri_str, expect);
++	free(uri_str);
++	TEST_DONE();
++}
++
++static void
++test_generate_valid(void)
++{
++	/* path arguments */
++	check_gen("pkcs11:id=%01",
++	    compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
++	check_gen("pkcs11:id=%00%01",
++	    compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
++	check_gen("pkcs11:token=SSH%20Keys", /* space must be percent encoded */
++	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
++	/* library-manufacturer is not implmented now */
++	/*check_gen("pkcs11:library-manufacturer=OpenSC",
++	    compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL, NULL));*/
++	check_gen("pkcs11:manufacturer=piv_II",
++	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL, NULL));
++	check_gen("pkcs11:serial=IamSerial",
++	    compose_uri(NULL, 0, NULL, NULL, NULL, "IamSerial", NULL, NULL, NULL));
++	check_gen("pkcs11:object=RSA%20Key",
++	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, "RSA Key", NULL));
++	/* query arguments */
++	check_gen("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
++	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
++
++	/* combinations */
++	check_gen("pkcs11:id=%02;token=SSH%20Keys",
++	    compose_uri("\x02", 1, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
++	check_gen("pkcs11:id=%EE%02?module-path=/usr/lib64/p11-kit-proxy.so",
++	    compose_uri("\xEE\x02", 2, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
++	check_gen("pkcs11:object=Encryption%20Key;manufacturer=piv_II",
++	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, "Encryption Key", NULL));
++
++	/* empty path component matches everything */
++	check_gen("pkcs11:", EMPTY_URI);
++
++}
++
++void
++check_encode(char *source, size_t len, char *allow_list, char *expect)
++{
++	char *buf = NULL;
++	struct sshbuf *b;
++
++	asprintf(&buf, "percent_encode: expected %s", expect);
++	TEST_START(buf);
++	free(buf);
++
++	b = percent_encode(source, len, allow_list);
++	ASSERT_STRING_EQ(sshbuf_ptr(b), expect);
++	sshbuf_free(b);
++	TEST_DONE();
++}
++
++static void
++test_percent_encode_multibyte(void)
++{
++	/* SHOULD be encoded as octets according to the UTF-8 character encoding */
++
++	/* multi-byte characters are "for free" */
++	check_encode("$", 1, "", "%24");
++	check_encode("¢", 2, "", "%C2%A2");
++	check_encode("€", 3, "", "%E2%82%AC");
++	check_encode("𐍈", 4, "", "%F0%90%8D%88");
++
++	/* CK_UTF8CHAR is unsigned char (1 byte) */
++	/* labels SHOULD be normalized to NFC [UAX15] */
++
++}
++
++static void
++test_percent_encode(void)
++{
++	/* Without allow list encodes everything (for CKA_ID) */
++	check_encode("A*", 2, "", "%41%2A");
++	check_encode("\x00", 1, "", "%00");
++	check_encode("\x7F", 1, "", "%7F");
++	check_encode("\x80", 1, "", "%80");
++	check_encode("\xff", 1, "", "%FF");
++
++	/* Default allow list encodes anything but safe letters */
++	check_encode("test" "\x00" "0alpha", 11, PKCS11_URI_WHITELIST,
++	    "test%000alpha");
++	check_encode(" ", 1, PKCS11_URI_WHITELIST,
++	    "%20"); /* Space MUST be percent encoded */
++	check_encode("/", 1, PKCS11_URI_WHITELIST,
++	    "%2F"); /* '/' delimiter MUST be percent encoded (in the path) */
++	check_encode("?", 1, PKCS11_URI_WHITELIST,
++	    "%3F"); /* delimiter '?' MUST be percent encoded (in the path) */
++	check_encode("#", 1, PKCS11_URI_WHITELIST,
++	    "%23"); /* '#' MUST be always percent encoded */
++	check_encode("key=value;separator?query&amp;#anch", 35, PKCS11_URI_WHITELIST,
++	    "key%3Dvalue%3Bseparator%3Fquery%26amp%3B%23anch");
++
++	/* Components in query can have '/' unencoded (useful for paths) */
++	check_encode("/path/to.file", 13, PKCS11_URI_WHITELIST "/",
++	    "/path/to.file");
++}
++
++void
++check_decode(char *source, char *expect, int expect_len)
++{
++	char *buf = NULL, *out = NULL;
++	int rv;
++
++	asprintf(&buf, "percent_decode: %s", source);
++	TEST_START(buf);
++	free(buf);
++
++	rv = percent_decode(source, &out);
++	ASSERT_INT_EQ(rv, expect_len);
++	if (rv >= 0)
++		ASSERT_MEM_EQ(out, expect, expect_len);
++	free(out);
++	TEST_DONE();
++}
++
++static void
++test_percent_decode(void)
++{
++	/* simple valid cases */
++	check_decode("%00", "\x00", 1);
++	check_decode("%FF", "\xFF", 1);
++
++	/* normal strings shold be kept intact */
++	check_decode("strings are left", "strings are left", 16);
++	check_decode("10%25 of trees", "10% of trees", 12);
++
++	/* make sure no more than 2 bytes are parsed */
++	check_decode("%222", "\x22" "2", 2);
++
++	/* invalid expects failure */
++	check_decode("%0", "", -1);
++	check_decode("%Z", "", -1);
++	check_decode("%FG", "", -1);
++}
++
++void
++tests(void)
++{
++	test_percent_encode();
++	test_percent_encode_multibyte();
++	test_percent_decode();
++	test_parse_valid();
++	test_parse_invalid();
++	test_generate_valid();
++}
++
++void
++benchmarks(void)
++{
++	printf("no benchmarks\n");
++}
++
+diff --git a/ssh-add.c b/ssh-add.c
+index 7ce451036..5eb564a7e 100644
+--- a/ssh-add.c
++++ b/ssh-add.c
+@@ -70,6 +70,7 @@
+ #include "ssh-sk.h"
+ #include "sk-api.h"
+ #include "hostfile.h"
++#include "ssh-pkcs11-uri.h"
+ 
+ #define CERT_EXPIRY_GRACE	(5*60)
+ 
+@@ -272,6 +273,38 @@ check_cert_lifetime(const struct sshkey *cert, int cert_lifetime)
+ 	return MINIMUM(cert_lifetime, (int)n);
+ }
+ 
++#ifdef ENABLE_PKCS11
++static int
++update_card(int agent_fd, int add, const char *id, int qflag,
++    int key_only, int cert_only,
++    struct dest_constraint **dest_constraints, size_t ndest_constraints,
++    struct sshkey **certs, size_t ncerts, char *pin);
++
++int
++update_pkcs11_uri(int agent_fd, int adding, const char *pkcs11_uri, int qflag,
++    struct dest_constraint **dest_constraints, size_t ndest_constraints)
++{
++	char *pin = NULL;
++	struct pkcs11_uri *uri;
++
++	/* dry-run parse to make sure the URI is valid and to report errors */
++	uri = pkcs11_uri_init();
++	if (pkcs11_uri_parse((char *) pkcs11_uri, uri) != 0)
++		fatal("Failed to parse PKCS#11 URI");
++	if (uri->pin != NULL) {
++		pin = strdup(uri->pin);
++		if (pin == NULL) {
++			fatal("Failed to dupplicate string");
++		}
++		/* pin is freed in the update_card() */
++	}
++	pkcs11_uri_cleanup(uri);
++
++	return update_card(agent_fd, adding, pkcs11_uri, qflag, 1, 0,
++	           dest_constraints, ndest_constraints, NULL, 0, pin);
++}
++#endif
++
+ static int
+ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
+     int qflag, int Nflag, const char *skprovider,
+@@ -462,15 +495,14 @@ static int
+ update_card(int agent_fd, int add, const char *id, int qflag,
+     int key_only, int cert_only,
+     struct dest_constraint **dest_constraints, size_t ndest_constraints,
+-    struct sshkey **certs, size_t ncerts)
++    struct sshkey **certs, size_t ncerts, char *pin)
+ {
+-	char *pin = NULL;
+ 	int r, ret = -1;
+ 
+ 	if (key_only)
+ 		ncerts = 0;
+ 
+-	if (add) {
++	if (add && pin == NULL) {
+ 		if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
+ 		    RP_ALLOW_STDIN)) == NULL)
+ 			return -1;
+@@ -652,6 +684,14 @@ do_file(int agent_fd, int deleting, int key_only, int cert_only,
+     char *file, int qflag, int Nflag, const char *skprovider,
+     struct dest_constraint **dest_constraints, size_t ndest_constraints)
+ {
++#ifdef ENABLE_PKCS11
++	if (strlen(file) >= strlen(PKCS11_URI_SCHEME) &&
++	    strncmp(file, PKCS11_URI_SCHEME,
++	    strlen(PKCS11_URI_SCHEME)) == 0) {
++		return update_pkcs11_uri(agent_fd, !deleting, file, qflag,
++                   dest_constraints, ndest_constraints);
++	}
++#endif
+ 	if (deleting) {
+ 		if (delete_file(agent_fd, file, key_only,
+ 		    cert_only, qflag) == -1)
+@@ -1004,7 +1044,7 @@ main(int argc, char **argv)
+ 		if (update_card(agent_fd, !deleting, pkcs11provider,
+ 		    qflag, key_only, cert_only,
+ 		    dest_constraints, ndest_constraints,
+-		    certs, ncerts) == -1)
++		    certs, ncerts, NULL) == -1)
+ 			ret = 1;
+ 		for (n = 0; n < ncerts; n++)
+ 			sshkey_free(certs[n]);
+diff --git a/ssh-agent.c b/ssh-agent.c
+index 17b2a96c0..1f1fc024d 100644
+--- a/ssh-agent.c
++++ b/ssh-agent.c
+@@ -1553,10 +1553,75 @@ add_p11_identity(struct sshkey *key, char *comment, const char *provider,
+ 	idtab->nentries++;
+ }
+ 
++static char *
++sanitize_pkcs11_provider(const char *provider)
++{
++	struct pkcs11_uri *uri = NULL;
++	char *sane_uri, *module_path = NULL; /* default path */
++	char canonical_provider[PATH_MAX];
++
++	if (provider == NULL)
++		return NULL;
++
++	memset(canonical_provider, 0, sizeof(canonical_provider));
++
++	if (strlen(provider) >= strlen(PKCS11_URI_SCHEME) &&
++	    strncmp(provider, PKCS11_URI_SCHEME,
++	    strlen(PKCS11_URI_SCHEME)) == 0) {
++		/* PKCS#11 URI */
++		uri = pkcs11_uri_init();
++		if (uri == NULL) {
++			error("Failed to init PKCS#11 URI");
++			return NULL;
++		}
++
++		if (pkcs11_uri_parse(provider, uri) != 0) {
++			error("Failed to parse PKCS#11 URI");
++			pkcs11_uri_cleanup(uri);
++			return NULL;
++		}
++		/* validate also provider from URI */
++		if (uri->module_path)
++			module_path = strdup(uri->module_path);
++	} else
++		module_path = strdup(provider); /* simple path */
++
++	if (module_path != NULL) { /* do not validate default NULL path in URI */
++		if (realpath(module_path, canonical_provider) == NULL) {
++			verbose("failed PKCS#11 provider \"%.100s\": realpath: %s",
++			    module_path, strerror(errno));
++			free(module_path);
++			pkcs11_uri_cleanup(uri);
++			return NULL;
++		}
++		free(module_path);
++		if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) {
++			verbose("refusing PKCS#11 provider \"%.100s\": "
++			    "not allowed", canonical_provider);
++			pkcs11_uri_cleanup(uri);
++			return NULL;
++		}
++
++		/* copy verified and sanitized provider path back to the uri */
++		if (uri) {
++			free(uri->module_path);
++			uri->module_path = xstrdup(canonical_provider);
++		}
++	}
++
++	if (uri) {
++		sane_uri = pkcs11_uri_get(uri);
++		pkcs11_uri_cleanup(uri);
++		return sane_uri;
++	} else {
++		return xstrdup(canonical_provider); /* simple path */
++	}
++}
++
+ static void
+ process_add_smartcard_key(SocketEntry *e)
+ {
+-	char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
++	char *provider = NULL, *pin = NULL, *sane_uri = NULL;
+ 	char **comments = NULL;
+ 	int r, i, count = 0, success = 0, confirm = 0;
+ 	u_int seconds = 0;
+@@ -1585,25 +1650,18 @@ process_add_smartcard_key(SocketEntry *e)
+ 		    "providers is disabled", provider);
+ 		goto send;
+ 	}
+-	if (realpath(provider, canonical_provider) == NULL) {
+-		verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
+-		    provider, strerror(errno));
+-		goto send;
+-	}
+-	if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) {
+-		verbose("refusing PKCS#11 add of \"%.100s\": "
+-		    "provider not allowed", canonical_provider);
++	sane_uri = sanitize_pkcs11_provider(provider);
++	if (sane_uri == NULL)
+ 		goto send;
+-	}
+-	debug_f("add %.100s", canonical_provider);
+ 	if (lifetime && !death)
+ 		death = monotime() + lifetime;
+ 
+-	count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments);
++	debug_f("add %.100s", sane_uri);
++	count = pkcs11_add_provider(sane_uri, pin, &keys, &comments);
+ 	for (i = 0; i < count; i++) {
+ 		if (comments[i] == NULL || comments[i][0] == '\0') {
+ 			free(comments[i]);
+-			comments[i] = xstrdup(canonical_provider);
++			comments[i] = xstrdup(sane_uri);
+ 		}
+ 		for (j = 0; j < ncerts; j++) {
+ 			if (!sshkey_is_cert(certs[j]))
+@@ -1613,13 +1671,13 @@ process_add_smartcard_key(SocketEntry *e)
+ 			if (pkcs11_make_cert(keys[i], certs[j], &k) != 0)
+ 				continue;
+ 			add_p11_identity(k, xstrdup(comments[i]),
+-			    canonical_provider, death, confirm,
++			    sane_uri, death, confirm,
+ 			    dest_constraints, ndest_constraints);
+ 			success = 1;
+ 		}
+ 		if (!cert_only && lookup_identity(keys[i]) == NULL) {
+ 			add_p11_identity(keys[i], comments[i],
+-			    canonical_provider, death, confirm,
++			    sane_uri, death, confirm,
+ 			    dest_constraints, ndest_constraints);
+ 			keys[i] = NULL;		/* transferred */
+ 			comments[i] = NULL;	/* transferred */
+@@ -1632,6 +1690,7 @@ process_add_smartcard_key(SocketEntry *e)
+ send:
+ 	free(pin);
+ 	free(provider);
++	free(sane_uri);
+ 	free(keys);
+ 	free(comments);
+ 	free_dest_constraints(dest_constraints, ndest_constraints);
+@@ -1644,8 +1703,8 @@ send:
+ static void
+ process_remove_smartcard_key(SocketEntry *e)
+ {
+-	char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
+-	int r, success = 0;
++	char *provider = NULL, *pin = NULL, *sane_uri = NULL;
++	int r, success = 0, removed_count = 0;
+ 	Identity *id, *nxt;
+ 
+ 	debug2_f("entering");
+@@ -1656,30 +1715,38 @@ process_remove_smartcard_key(SocketEntry *e)
+ 	}
+ 	free(pin);
+ 
+-	if (realpath(provider, canonical_provider) == NULL) {
+-		verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
+-		    provider, strerror(errno));
++	sane_uri = sanitize_pkcs11_provider(provider);
++	if (sane_uri == NULL)
+ 		goto send;
+-	}
+ 
+-	debug_f("remove %.100s", canonical_provider);
++	debug_f("remove %.100s", sane_uri);
+ 	for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) {
+ 		nxt = TAILQ_NEXT(id, next);
+ 		/* Skip file--based keys */
+ 		if (id->provider == NULL)
+ 			continue;
+-		if (!strcmp(canonical_provider, id->provider)) {
++		if (!strcmp(sane_uri, id->provider)) {
+ 			TAILQ_REMOVE(&idtab->idlist, id, next);
+ 			free_identity(id);
+ 			idtab->nentries--;
++			removed_count++;
+ 		}
+ 	}
+-	if (pkcs11_del_provider(canonical_provider) == 0)
++	/* Only succeed if we actually removed keys and unloaded provider */
++	if (removed_count > 0 && pkcs11_del_provider(sane_uri) == 0) {
+ 		success = 1;
+-	else
++		debug_f("removed %d key(s) from provider %s",
++		    removed_count, sane_uri);
++	} else if (removed_count == 0) {
++		error_f("no matching keys found for provider %s", sane_uri);
++		/* Still try to clean up provider if it exists */
++		pkcs11_del_provider(sane_uri);
++	} else {
+ 		error_f("pkcs11_del_provider failed");
++	}
+ send:
+ 	free(provider);
++	free(sane_uri);
+ 	send_status(e, success);
+ }
+ #endif /* ENABLE_PKCS11 */
+diff --git a/ssh-keygen.c b/ssh-keygen.c
+index eb880dbc0..25906f3c1 100644
+--- a/ssh-keygen.c
++++ b/ssh-keygen.c
+@@ -835,8 +835,11 @@ do_download(struct passwd *pw)
+ 			free(fp);
+ 		} else {
+ 			(void) sshkey_write(keys[i], stdout); /* XXX check */
+-			fprintf(stdout, "%s%s\n",
+-			    *(comments[i]) == '\0' ? "" : " ", comments[i]);
++			if (*(comments[i]) != '\0') {
++				fprintf(stdout, " %s", comments[i]);
++			}
++			(void) pkcs11_uri_write(keys[i], stdout);
++			fprintf(stdout, "\n");
+ 		}
+ 		free(comments[i]);
+ 		sshkey_free(keys[i]);
+diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c
+index 30a4cf5dc..3fd058a0c 100644
+--- a/ssh-pkcs11-client.c
++++ b/ssh-pkcs11-client.c
+@@ -18,6 +18,7 @@
+ 
+ #include "includes.h"
+ 
++#ifdef ENABLE_PKCS11
+ #include <sys/types.h>
+ #include <sys/time.h>
+ #include <sys/socket.h>
+@@ -38,6 +39,7 @@
+ #include "authfd.h"
+ #include "atomicio.h"
+ #include "ssh-pkcs11.h"
++#include "ssh-pkcs11-uri.h"
+ #include "ssherr.h"
+ 
+ /* borrows code from sftp-server and ssh-agent */
+@@ -379,6 +381,19 @@ pkcs11_start_helper(const char *path)
+ 	return helper;
+ }
+ 
++int
++pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin, struct sshkey ***keyp, char ***labelsp)
++{
++	int nkeys = 0;
++	char *provider_uri = pkcs11_uri_get(uri);
++
++	debug_f("called, provider_uri = %s", provider_uri);
++
++	nkeys = pkcs11_add_provider(provider_uri, pin, keyp, labelsp);
++
++	return nkeys;
++}
++
+ int
+ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
+     char ***labelsp)
+@@ -390,6 +405,8 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
+ 	struct sshbuf *msg;
+ 	struct helper *helper;
+ 
++	debug_f("called, name = %s", name);
++
+ 	if ((helper = helper_by_provider(name)) == NULL &&
+ 	    (helper = pkcs11_start_helper(name)) == NULL)
+ 		return -1;
+@@ -414,6 +431,7 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
+ 		*keysp = xcalloc(nkeys, sizeof(struct sshkey *));
+ 		if (labelsp)
+ 			*labelsp = xcalloc(nkeys, sizeof(char *));
++		debug_f("nkeys = %u", nkeys);
+ 		for (i = 0; i < nkeys; i++) {
+ 			/* XXX clean up properly instead of fatal() */
+ 			if ((r = sshkey_froms(msg, &k)) != 0 ||
+@@ -497,3 +515,4 @@ pkcs11_key_free(struct sshkey *key)
+ 	if (helper->nkeyblobs == 0)
+ 		helper_terminate(helper);
+ }
++#endif
+diff --git a/ssh-pkcs11-uri.c b/ssh-pkcs11-uri.c
+new file mode 100644
+index 000000000..d5b074592
+--- /dev/null
++++ b/ssh-pkcs11-uri.c
+@@ -0,0 +1,438 @@
++/*
++ * Copyright (c) 2017 Red Hat
++ *
++ * Authors: Jakub Jelen <jjelen@redhat.com>
++ *
++ * 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 "includes.h"
++
++#ifdef ENABLE_PKCS11
++
++#include <stdio.h>
++#include <string.h>
++#include <stdlib.h>
++
++#include "sshkey.h"
++#include "sshbuf.h"
++#include "log.h"
++
++#define CRYPTOKI_COMPAT
++#include "pkcs11.h"
++
++#include "ssh-pkcs11-uri.h"
++
++#define PKCS11_URI_PATH_SEPARATOR ";"
++#define PKCS11_URI_QUERY_SEPARATOR "&"
++#define PKCS11_URI_VALUE_SEPARATOR "="
++#define PKCS11_URI_ID "id"
++#define PKCS11_URI_TOKEN "token"
++#define PKCS11_URI_OBJECT "object"
++#define PKCS11_URI_LIB_MANUF "library-manufacturer"
++#define PKCS11_URI_MANUF "manufacturer"
++#define PKCS11_URI_SERIAL "serial"
++#define PKCS11_URI_MODULE_PATH "module-path"
++#define PKCS11_URI_PIN_VALUE "pin-value"
++
++/* Keyword tokens. */
++typedef enum {
++	pId, pToken, pObject, pLibraryManufacturer, pManufacturer, pSerial,
++	pModulePath, pPinValue, pBadOption
++} pkcs11uriOpCodes;
++
++/* Textual representation of the tokens. */
++static struct {
++	const char *name;
++	pkcs11uriOpCodes opcode;
++} keywords[] = {
++	{ PKCS11_URI_ID, pId },
++	{ PKCS11_URI_TOKEN, pToken },
++	{ PKCS11_URI_OBJECT, pObject },
++	{ PKCS11_URI_LIB_MANUF, pLibraryManufacturer },
++	{ PKCS11_URI_MANUF, pManufacturer },
++	{ PKCS11_URI_SERIAL, pSerial },
++	{ PKCS11_URI_MODULE_PATH, pModulePath },
++	{ PKCS11_URI_PIN_VALUE, pPinValue },
++	{ NULL, pBadOption }
++};
++
++static pkcs11uriOpCodes
++parse_token(const char *cp)
++{
++	u_int i;
++
++	for (i = 0; keywords[i].name; i++)
++		if (strncasecmp(cp, keywords[i].name,
++		    strlen(keywords[i].name)) == 0)
++			return keywords[i].opcode;
++
++	return pBadOption;
++}
++
++int
++percent_decode(char *data, char **outp)
++{
++	char tmp[3];
++	char *out, *tmp_end;
++	char *p = data;
++	long value;
++	size_t outlen = 0;
++
++	out = malloc(strlen(data)+1); /* upper bound */
++	if (out == NULL)
++		return -1;
++	while (*p != '\0') {
++		switch (*p) {
++		case '%':
++			p++;
++			if (*p == '\0')
++				goto fail;
++			tmp[0] = *p++;
++			if (*p == '\0')
++				goto fail;
++			tmp[1] = *p++;
++			tmp[2] = '\0';
++			tmp_end = NULL;
++			value = strtol(tmp, &tmp_end, 16);
++			if (tmp_end != tmp+2)
++				goto fail;
++			else
++				out[outlen++] = (char) value;
++			break;
++		default:
++			out[outlen++] = *p++;
++			break;
++		}
++	}
++
++	/* zero terminate */
++	out[outlen] = '\0';
++	*outp = out;
++	return outlen;
++fail:
++	free(out);
++	return -1;
++}
++
++struct sshbuf *
++percent_encode(const char *data, size_t length, const char *allow_list)
++{
++	struct sshbuf *b = NULL;
++	char tmp[4], *cp;
++	size_t i;
++
++	if ((b = sshbuf_new()) == NULL)
++		return NULL;
++	for (i = 0; i < length; i++) {
++		cp = strchr(allow_list, data[i]);
++		/* if c is specified as '\0' pointer to terminator is returned !! */
++		if (cp != NULL && *cp != '\0') {
++			if (sshbuf_put(b, &data[i], 1) != 0)
++				goto err;
++		} else
++			if (snprintf(tmp, 4, "%%%02X", (unsigned char) data[i]) < 3
++			    || sshbuf_put(b, tmp, 3) != 0)
++				goto err;
++	}
++	if (sshbuf_put(b, "\0", 1) == 0)
++		return b;
++err:
++	sshbuf_free(b);
++	return NULL;
++}
++
++char *
++pkcs11_uri_append(char *part, const char *separator, const char *key,
++    struct sshbuf *value)
++{
++	char *new_part;
++	size_t size = 0;
++
++	if (value == NULL)
++		return NULL;
++
++	size = asprintf(&new_part,
++	    "%s%s%s"  PKCS11_URI_VALUE_SEPARATOR "%s",
++	    (part != NULL ? part : ""),
++	    (part != NULL ? separator : ""),
++	    key, sshbuf_ptr(value));
++	sshbuf_free(value);
++	free(part);
++
++	if (size <= 0)
++		return NULL;
++	return new_part;
++}
++
++char *
++pkcs11_uri_get(struct pkcs11_uri *uri)
++{
++	size_t size = 0;
++	char *p = NULL, *path = NULL, *query = NULL;
++
++	/* compose a percent-encoded ID */
++	if (uri->id_len > 0) {
++		struct sshbuf *key_id = percent_encode(uri->id, uri->id_len, "");
++		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++		    PKCS11_URI_ID, key_id);
++		if (path == NULL)
++			goto err;
++	}
++
++	/* Write object label */
++	if (uri->object) {
++		struct sshbuf *label = percent_encode(uri->object, strlen(uri->object),
++		    PKCS11_URI_WHITELIST);
++		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++		    PKCS11_URI_OBJECT, label);
++		if (path == NULL)
++			goto err;
++	}
++
++	/* Write token label */
++	if (uri->token) {
++		struct sshbuf *label = percent_encode(uri->token, strlen(uri->token),
++		    PKCS11_URI_WHITELIST);
++		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++		    PKCS11_URI_TOKEN, label);
++		if (path == NULL)
++			goto err;
++	}
++
++	/* Write manufacturer */
++	if (uri->manuf) {
++		struct sshbuf *manuf = percent_encode(uri->manuf,
++		    strlen(uri->manuf), PKCS11_URI_WHITELIST);
++		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++		    PKCS11_URI_MANUF, manuf);
++		if (path == NULL)
++			goto err;
++	}
++
++	/* Write serial */
++	if (uri->serial) {
++		struct sshbuf *serial = percent_encode(uri->serial,
++		    strlen(uri->serial), PKCS11_URI_WHITELIST);
++		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
++		    PKCS11_URI_SERIAL, serial);
++		if (path == NULL)
++			goto err;
++	}
++
++	/* Write module_path */
++	if (uri->module_path) {
++		struct sshbuf *module = percent_encode(uri->module_path,
++		    strlen(uri->module_path), PKCS11_URI_WHITELIST "/");
++		query = pkcs11_uri_append(query, PKCS11_URI_QUERY_SEPARATOR,
++		    PKCS11_URI_MODULE_PATH, module);
++		if (query == NULL)
++			goto err;
++	}
++
++	size = asprintf(&p, PKCS11_URI_SCHEME "%s%s%s",
++	    path != NULL ? path : "",
++	    query != NULL ? "?" : "",
++	    query != NULL ? query : "");
++err:
++	free(query);
++	free(path);
++	if (size <= 0)
++		return NULL;
++	return p;
++}
++
++struct pkcs11_uri *
++pkcs11_uri_init()
++{
++	struct pkcs11_uri *d = calloc(1, sizeof(struct pkcs11_uri));
++	return d;
++}
++
++void
++pkcs11_uri_cleanup(struct pkcs11_uri *pkcs11)
++{
++	if (pkcs11 == NULL) {
++		return;
++	}
++
++	free(pkcs11->id);
++	free(pkcs11->module_path);
++	free(pkcs11->token);
++	free(pkcs11->object);
++	free(pkcs11->lib_manuf);
++	free(pkcs11->manuf);
++	free(pkcs11->serial);
++	if (pkcs11->pin)
++		freezero(pkcs11->pin, strlen(pkcs11->pin));
++	free(pkcs11);
++}
++
++int
++pkcs11_uri_parse(const char *uri, struct pkcs11_uri *pkcs11)
++{
++	char *saveptr1, *saveptr2, *str1, *str2, *tok;
++	int rv = 0, len;
++	char *p = NULL;
++
++	size_t scheme_len = strlen(PKCS11_URI_SCHEME);
++	if (strlen(uri) < scheme_len || /* empty URI matches everything */
++	    strncmp(uri, PKCS11_URI_SCHEME, scheme_len) != 0) {
++		error_f("The '%s' does not look like PKCS#11 URI", uri);
++		return -1;
++	}
++
++	if (pkcs11 == NULL) {
++		error_f("Bad arguments. The pkcs11 can't be null");
++		return -1;
++	}
++
++	/* skip URI schema name */
++	p = strdup(uri);
++	str1 = p;
++
++	/* everything before ? */
++	tok = strtok_r(str1, "?", &saveptr1);
++	if (tok == NULL) {
++		error_f("pk11-path expected, got EOF");
++		rv = -1;
++		goto out;
++	}
++
++	/* skip URI schema name:
++	 * the scheme ensures that there is at least something before "?"
++	 * allowing empty pk11-path. Resulting token at worst pointing to
++	 * \0 byte */
++	tok = tok + scheme_len;
++
++	/* parse pk11-path */
++	for (str2 = tok; ; str2 = NULL) {
++		char **charptr, *arg = NULL;
++		pkcs11uriOpCodes opcode;
++		tok = strtok_r(str2, PKCS11_URI_PATH_SEPARATOR, &saveptr2);
++		if (tok == NULL)
++			break;
++		opcode = parse_token(tok);
++		if (opcode != pBadOption)
++			arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
++
++		switch (opcode) {
++		case pId:
++			/* CKA_ID */
++			if (pkcs11->id != NULL) {
++				verbose_f("The id already set in the PKCS#11 URI");
++				rv = -1;
++				goto out;
++			}
++			len = percent_decode(arg, &pkcs11->id);
++			if (len <= 0) {
++				verbose_f("Failed to percent-decode CKA_ID: %s", arg);
++				rv = -1;
++				goto out;
++			} else
++				pkcs11->id_len = len;
++			debug3_f("Setting CKA_ID = %s from PKCS#11 URI", arg);
++			break;
++		case pToken:
++			/* CK_TOKEN_INFO -> label */
++			charptr = &pkcs11->token;
++ parse_string:
++			if (*charptr != NULL) {
++				verbose_f("The %s already set in the PKCS#11 URI",
++				    keywords[opcode].name);
++				rv = -1;
++				goto out;
++			}
++			percent_decode(arg, charptr);
++			debug3_f("Setting %s = %s from PKCS#11 URI",
++			    keywords[opcode].name, *charptr);
++			break;
++
++		case pObject:
++			/* CK_TOKEN_INFO -> manufacturerID */
++			charptr = &pkcs11->object;
++			goto parse_string;
++
++		case pManufacturer:
++			/* CK_TOKEN_INFO -> manufacturerID */
++			charptr = &pkcs11->manuf;
++			goto parse_string;
++
++		case pSerial:
++			/* CK_TOKEN_INFO -> serialNumber */
++			charptr = &pkcs11->serial;
++			goto parse_string;
++
++		case pLibraryManufacturer:
++			/* CK_INFO -> manufacturerID */
++			charptr = &pkcs11->lib_manuf;
++			goto parse_string;
++
++		default:
++			/* Unrecognized attribute in the URI path SHOULD be error */
++			verbose_f("Unknown part of path in PKCS#11 URI: %s", tok);
++		}
++	}
++
++	tok = strtok_r(NULL, "?", &saveptr1);
++	if (tok == NULL) {
++		goto out;
++	}
++	/* parse pk11-query (optional) */
++	for (str2 = tok; ; str2 = NULL) {
++		char *arg;
++		pkcs11uriOpCodes opcode;
++		tok = strtok_r(str2, PKCS11_URI_QUERY_SEPARATOR, &saveptr2);
++		if (tok == NULL)
++			break;
++		opcode = parse_token(tok);
++		if (opcode != pBadOption)
++			arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
++
++		switch (opcode) {
++		case pModulePath:
++			/* module-path is PKCS11Provider */
++			if (pkcs11->module_path != NULL) {
++				verbose_f("Multiple module-path attributes are"
++				    "not supported the PKCS#11 URI");
++				rv = -1;
++				goto out;
++			}
++			percent_decode(arg, &pkcs11->module_path);
++			debug3_f("Setting PKCS11Provider = %s from PKCS#11 URI",
++			    pkcs11->module_path);
++			break;
++
++		case pPinValue:
++			/* pin-value */
++			if (pkcs11->pin != NULL) {
++				verbose_f("Multiple pin-value attributes are"
++				    "not supported the PKCS#11 URI");
++				rv = -1;
++				goto out;
++			}
++			percent_decode(arg, &pkcs11->pin);
++			debug3_f("Setting PIN from PKCS#11 URI");
++			break;
++
++		default:
++			/* Unrecognized attribute in the URI query SHOULD be ignored */
++			verbose_f("Unknown part of query in PKCS#11 URI: %s", tok);
++		}
++	}
++out:
++	free(p);
++	return rv;
++}
++
++#endif /* ENABLE_PKCS11 */
+diff --git a/ssh-pkcs11-uri.h b/ssh-pkcs11-uri.h
+new file mode 100644
+index 000000000..bc758e760
+--- /dev/null
++++ b/ssh-pkcs11-uri.h
+@@ -0,0 +1,46 @@
++/*
++ * Copyright (c) 2017 Red Hat
++ *
++ * Authors: Jakub Jelen <jjelen@redhat.com>
++ *
++ * 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.
++ */
++
++#ifndef _SSH_PKCS11_URI_H
++#define _SSH_PKCS11_URI_H
++
++#define PKCS11_URI_SCHEME "pkcs11:"
++#define PKCS11_URI_WHITELIST	"abcdefghijklmnopqrstuvwxyz" \
++				"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
++				"0123456789_-.()"
++
++struct pkcs11_uri {
++	/* path */
++	char *id;
++	size_t id_len;
++	char *token;
++	char *object;
++	char *lib_manuf;
++	char *manuf;
++	char *serial;
++	/* query */
++	char *module_path;
++	char *pin; /* Only parsed, but not printed */
++};
++
++struct	 pkcs11_uri *pkcs11_uri_init();
++void	 pkcs11_uri_cleanup(struct pkcs11_uri *);
++int	 pkcs11_uri_parse(const char *, struct pkcs11_uri *);
++struct	 pkcs11_uri *pkcs11_uri_init();
++char	*pkcs11_uri_get(struct pkcs11_uri *uri);
++#endif /* _SSH_PKCS11_URI_H */
+diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
+index 7a7d3b8ea..6f23fc38b 100644
+--- a/ssh-pkcs11.c
++++ b/ssh-pkcs11.c
+@@ -36,6 +36,7 @@
+ #include <openssl/ecdsa.h>
+ #include <openssl/x509.h>
+ #include <openssl/err.h>
++#include <openssl/evp.h>
+ #endif
+ 
+ #define CRYPTOKI_COMPAT
+@@ -48,6 +49,7 @@
+ #include "misc.h"
+ #include "sshbuf.h"
+ #include "ssh-pkcs11.h"
++#include "ssh-pkcs11-uri.h"
+ #include "digest.h"
+ #include "xmalloc.h"
+ #include "crypto_api.h"
+@@ -58,8 +60,8 @@ struct pkcs11_slotinfo {
+ 	int			logged_in;
+ };
+ 
+-struct pkcs11_provider {
+-	char			*name;
++struct pkcs11_module {
++	char			*module_path;
+ 	void			*handle;
+ 	CK_FUNCTION_LIST	*function_list;
+ 	CK_INFO			info;
+@@ -68,6 +70,13 @@ struct pkcs11_provider {
+ 	struct pkcs11_slotinfo	*slotinfo;
+ 	int			valid;
+ 	int			refcount;
++};
++
++struct pkcs11_provider {
++	char			*name;
++	struct pkcs11_module	*module; /* can be shared between various providers */
++	int			refcount;
++	int			valid;
+ 	TAILQ_ENTRY(pkcs11_provider) next;
+ };
+ 
+@@ -79,6 +88,7 @@ struct pkcs11_key {
+ 	CK_ULONG		slotidx;
+ 	char			*keyid;
+ 	int			keyid_len;
++	char			*label;
+ 	TAILQ_ENTRY(pkcs11_key)	next;
+ };
+ 
+@@ -105,26 +115,61 @@ ossl_error(const char *msg)
+  * this is called when a provider gets unregistered.
+  */
+ static void
+-pkcs11_provider_finalize(struct pkcs11_provider *p)
++pkcs11_module_finalize(struct pkcs11_module *m)
+ {
+ 	CK_RV rv;
+ 	CK_ULONG i;
+ 
+-	debug_f("provider \"%s\" refcount %d valid %d",
+-	    p->name, p->refcount, p->valid);
+-	if (!p->valid)
++	debug_f("%p refcount %d valid %d", m, m->refcount, m->valid);
++	if (!m->valid)
+ 		return;
+-	for (i = 0; i < p->nslots; i++) {
+-		if (p->slotinfo[i].session &&
+-		    (rv = p->function_list->C_CloseSession(
+-		    p->slotinfo[i].session)) != CKR_OK)
++	for (i = 0; i < m->nslots; i++) {
++		if (m->slotinfo[i].session &&
++		    (rv = m->function_list->C_CloseSession(
++		    m->slotinfo[i].session)) != CKR_OK)
+ 			error("C_CloseSession failed: %lu", rv);
+ 	}
+-	if ((rv = p->function_list->C_Finalize(NULL)) != CKR_OK)
++	if ((rv = m->function_list->C_Finalize(NULL)) != CKR_OK)
+ 		error("C_Finalize failed: %lu", rv);
++	m->valid = 0;
++	m->function_list = NULL;
++	dlclose(m->handle);
++}
++
++/*
++ * remove a reference to the pkcs11 module.
++ * called when a provider is unregistered.
++ */
++static void
++pkcs11_module_unref(struct pkcs11_module *m)
++{
++	debug_f("%p refcount %d", m, m->refcount);
++	if (--m->refcount <= 0) {
++		pkcs11_module_finalize(m);
++		if (m->valid)
++			error_f("%p still valid", m);
++		free(m->slotlist);
++		free(m->slotinfo);
++		free(m->module_path);
++		free(m);
++	}
++}
++
++/*
++ * finalize a provider shared library, it's no longer usable.
++ * however, there might still be keys referencing this provider,
++ * so the actual freeing of memory is handled by pkcs11_provider_unref().
++ * this is called when a provider gets unregistered.
++ */
++static void
++pkcs11_provider_finalize(struct pkcs11_provider *p)
++{
++	debug_f("%p refcount %d valid %d", p, p->refcount, p->valid);
++	if (!p->valid)
++		return;
++	pkcs11_module_unref(p->module);
++	p->module = NULL;
+ 	p->valid = 0;
+-	p->function_list = NULL;
+-	dlclose(p->handle);
+ }
+ 
+ /*
+@@ -136,15 +181,27 @@ pkcs11_provider_unref(struct pkcs11_provider *p)
+ {
+ 	debug_f("provider \"%s\" refcount %d", p->name, p->refcount);
+ 	if (--p->refcount <= 0) {
+-		if (p->valid)
+-			error_f("provider \"%s\" still valid", p->name);
+ 		free(p->name);
+-		free(p->slotlist);
+-		free(p->slotinfo);
++		if (p->module)
++			pkcs11_module_unref(p->module);
+ 		free(p);
+ 	}
+ }
+ 
++/* lookup provider by module path */
++static struct pkcs11_module *
++pkcs11_provider_lookup_module(char *module_path)
++{
++	struct pkcs11_provider *p;
++
++	TAILQ_FOREACH(p, &pkcs11_providers, next) {
++		debug("check %p %s (%s)", p, p->name, p->module->module_path);
++		if (!strcmp(module_path, p->module->module_path))
++			return (p->module);
++	}
++	return (NULL);
++}
++
+ /* lookup provider by name */
+ static struct pkcs11_provider *
+ pkcs11_provider_lookup(char *provider_id)
+@@ -159,19 +216,55 @@ pkcs11_provider_lookup(char *provider_id)
+ 	return (NULL);
+ }
+ 
++int pkcs11_del_provider_by_uri(struct pkcs11_uri *);
++
+ /* unregister provider by name */
+ int
+ pkcs11_del_provider(char *provider_id)
++{
++	int rv;
++	struct pkcs11_uri *uri;
++
++	debug_f("called, provider_id = %s", provider_id);
++
++      if (provider_id == NULL)
++          return 0;
++
++	uri = pkcs11_uri_init();
++	if (uri == NULL)
++		fatal("Failed to init PKCS#11 URI");
++
++	if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
++	    strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
++		if (pkcs11_uri_parse(provider_id, uri) != 0)
++			fatal("Failed to parse PKCS#11 URI");
++	} else {
++		uri->module_path = strdup(provider_id);
++	}
++
++	rv = pkcs11_del_provider_by_uri(uri);
++	pkcs11_uri_cleanup(uri);
++	return rv;
++}
++
++/* unregister provider by PKCS#11 URI */
++int
++pkcs11_del_provider_by_uri(struct pkcs11_uri *uri)
+ {
+ 	struct pkcs11_provider *p;
++	int rv = -1;
++	char *provider_uri = pkcs11_uri_get(uri);
+ 
+-	if ((p = pkcs11_provider_lookup(provider_id)) != NULL) {
++	debug3_f("called with provider %s", provider_uri);
++
++	if ((p = pkcs11_provider_lookup(provider_uri)) != NULL) {
+ 		TAILQ_REMOVE(&pkcs11_providers, p, next);
+ 		pkcs11_provider_finalize(p);
+ 		pkcs11_provider_unref(p);
+-		return (0);
++		rv = 0;
+ 	}
+-	return (-1);
++	free(provider_uri);
++	return rv;
+ }
+ 
+ /* release a wrapped object */
+@@ -183,6 +276,7 @@ pkcs11_k11_free(struct pkcs11_key *k11)
+ 	if (k11->provider)
+ 		pkcs11_provider_unref(k11->provider);
+ 	free(k11->keyid);
++	free(k11->label);
+ 	sshbuf_free(k11->keyblob);
+ 	free(k11);
+ }
+@@ -198,8 +292,8 @@ pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr,
+ 	CK_RV			rv;
+ 	int			ret = -1;
+ 
+-	f = p->function_list;
+-	session = p->slotinfo[slotidx].session;
++	f = p->module->function_list;
++	session = p->module->slotinfo[slotidx].session;
+ 	if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) {
+ 		error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv);
+ 		return (-1);
+@@ -236,14 +330,14 @@ pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si,
+ 	if (si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH)
+ 		verbose("Deferring PIN entry to reader keypad.");
+ 	else {
+-		snprintf(prompt, sizeof(prompt), "Enter PIN for '%s': ",
++		snprintf(prompt, sizeof(prompt), "Enter PIN for '%.32s': ",
+ 		    si->token.label);
+-		if ((pin = read_passphrase(prompt, RP_ALLOW_EOF)) == NULL) {
++		if ((pin = read_passphrase(prompt, RP_ALLOW_EOF|RP_ALLOW_STDIN)) == NULL) {
+ 			debug_f("no pin specified");
+ 			return (-1);	/* bail out */
+ 		}
+ 	}
+-	rv = provider->function_list->C_Login(si->session, type, (u_char *)pin,
++	rv = provider->module->function_list->C_Login(si->session, type, (u_char *)pin,
+ 	    (pin != NULL) ? strlen(pin) : 0);
+ 	if (pin != NULL)
+ 		freezero(pin, strlen(pin));
+@@ -273,13 +367,14 @@ pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si,
+ static int
+ pkcs11_login(struct pkcs11_key *k11, CK_USER_TYPE type)
+ {
+-	if (k11 == NULL || k11->provider == NULL || !k11->provider->valid) {
++	if (k11 == NULL || k11->provider == NULL || !k11->provider->valid ||
++	    k11->provider->module == NULL || !k11->provider->module->valid) {
+ 		error("no pkcs11 (valid) provider found");
+ 		return (-1);
+ 	}
+ 
+ 	return pkcs11_login_slot(k11->provider,
+-	    &k11->provider->slotinfo[k11->slotidx], type);
++	    &k11->provider->module->slotinfo[k11->slotidx], type);
+ }
+ 
+ 
+@@ -295,13 +390,14 @@ pkcs11_check_obj_bool_attrib(struct pkcs11_key *k11, CK_OBJECT_HANDLE obj,
+ 
+ 	*val = 0;
+ 
+-	if (!k11->provider || !k11->provider->valid) {
++	if (!k11->provider || !k11->provider->valid ||
++	    !k11->provider->module || !k11->provider->module->valid) {
+ 		error("no pkcs11 (valid) provider found");
+ 		return (-1);
+ 	}
+ 
+-	f = k11->provider->function_list;
+-	si = &k11->provider->slotinfo[k11->slotidx];
++	f = k11->provider->module->function_list;
++	si = &k11->provider->module->slotinfo[k11->slotidx];
+ 
+ 	attr.type = type;
+ 	attr.pValue = &flag;
+@@ -332,13 +428,14 @@ pkcs11_get_key(struct pkcs11_key *k11, CK_MECHANISM_TYPE mech_type)
+ 	int			 always_auth = 0;
+ 	int			 did_login = 0;
+ 
+-	if (!k11->provider || !k11->provider->valid) {
++	if (!k11->provider || !k11->provider->valid ||
++	    !k11->provider->module || !k11->provider->module->valid) {
+ 		error("no pkcs11 (valid) provider found");
+ 		return (-1);
+ 	}
+ 
+-	f = k11->provider->function_list;
+-	si = &k11->provider->slotinfo[k11->slotidx];
++	f = k11->provider->module->function_list;
++	si = &k11->provider->module->slotinfo[k11->slotidx];
+ 
+ 	if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) {
+ 		if (pkcs11_login(k11, CKU_USER) < 0) {
+@@ -437,6 +534,12 @@ pkcs11_record_key(struct pkcs11_provider *provider, CK_ULONG slotidx,
+ 		k11->keyid = xmalloc(k11->keyid_len);
+ 		memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len);
+ 	}
++	if (keyid_attrib->ulValueLen > 0 ) {
++		k11->label = xmalloc(keyid_attrib->ulValueLen+1);
++		memcpy(k11->label, keyid_attrib->pValue, keyid_attrib->ulValueLen);
++		k11->label[keyid_attrib->ulValueLen] = 0;
++	}
++
+ 	TAILQ_INSERT_TAIL(&pkcs11_keys, k11, next);
+ 
+ 	return 0;
+@@ -464,6 +567,42 @@ pkcs11_lookup_key(struct sshkey *key)
+ 	return found;
+ }
+ 
++/*
++ * This can't be in the ssh-pkcs11-uri, becase we can not depend on
++ * PKCS#11 structures in ssh-agent (using client-helper communication)
++ */
++int
++pkcs11_uri_write(const struct sshkey *key, FILE *f)
++{
++	char *p = NULL;
++	struct pkcs11_uri uri;
++	struct pkcs11_key *k11 = pkcs11_lookup_key(key);
++
++	if (k11 == NULL) {
++		error("Failed to get ex_data for key type %d", key->type);
++		return (-1);
++	}
++
++	/* omit type -- we are looking for private-public or private-certificate pairs */
++	uri.id = k11->keyid;
++	uri.id_len = k11->keyid_len;
++	uri.token = k11->provider->module->slotinfo[k11->slotidx].token.label;
++	uri.object = k11->label;
++	uri.module_path = k11->provider->module->module_path;
++	uri.lib_manuf = k11->provider->module->info.manufacturerID;
++	uri.manuf = k11->provider->module->slotinfo[k11->slotidx].token.manufacturerID;
++	uri.serial = k11->provider->module->slotinfo[k11->slotidx].token.serialNumber;
++
++	p = pkcs11_uri_get(&uri);
++	/* do not cleanup -- we do not allocate here, only reference */
++	if (p == NULL)
++		return -1;
++
++	fprintf(f, " %s", p);
++	free(p);
++	return 0;
++}
++
+ #ifdef WITH_OPENSSL
+ /*
+  * See:
+@@ -568,8 +707,8 @@ pkcs11_sign_rsa(struct sshkey *key,
+ 		return SSH_ERR_AGENT_FAILURE;
+ 	}
+ 
+-	f = k11->provider->function_list;
+-	si = &k11->provider->slotinfo[k11->slotidx];
++	f = k11->provider->module->function_list;
++	si = &k11->provider->module->slotinfo[k11->slotidx];
+ 
+ 	if ((siglen = EVP_PKEY_size(key->pkey)) <= 0)
+ 		return SSH_ERR_INVALID_ARGUMENT;
+@@ -658,8 +797,8 @@ pkcs11_sign_ecdsa(struct sshkey *key,
+ 	debug3_f("sign using provider %s slotidx %lu",
+ 	    k11->provider->name, (u_long)k11->slotidx);
+ 
+-	f = k11->provider->function_list;
+-	si = &k11->provider->slotinfo[k11->slotidx];
++	f = k11->provider->module->function_list;
++	si = &k11->provider->module->slotinfo[k11->slotidx];
+ 
+ 	/* Prepare digest to be signed */
+ 	if ((hashalg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1)
+@@ -743,8 +882,8 @@ pkcs11_sign_ed25519(struct sshkey *key,
+ 	debug3_f("sign using provider %s slotidx %lu",
+ 	    k11->provider->name, (u_long)k11->slotidx);
+ 
+-	f = k11->provider->function_list;
+-	si = &k11->provider->slotinfo[k11->slotidx];
++	f = k11->provider->module->function_list;
++	si = &k11->provider->module->slotinfo[k11->slotidx];
+ 
+ 	xdata = xmalloc(datalen);
+ 	memcpy(xdata, data, datalen);
+@@ -772,7 +911,8 @@ pkcs11_sign_ed25519(struct sshkey *key,
+ 	return ret;
+ }
+ 
+-/* remove trailing spaces */
++/* remove trailing spaces. Note, that this does NOT guarantee the buffer
++ * will be null terminated if there are no trailing spaces! */
+ static char *
+ rmspace(u_char *buf, size_t len)
+ {
+@@ -804,8 +944,8 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin,
+ 	CK_SESSION_HANDLE	session;
+ 	int			login_required, ret;
+ 
+-	f = p->function_list;
+-	si = &p->slotinfo[slotidx];
++	f = p->module->function_list;
++	si = &p->module->slotinfo[slotidx];
+ 
+ 	login_required = si->token.flags & CKF_LOGIN_REQUIRED;
+ 
+@@ -815,9 +955,9 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin,
+ 		error("pin required");
+ 		return (-SSH_PKCS11_ERR_PIN_REQUIRED);
+ 	}
+-	if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION|
++	if ((rv = f->C_OpenSession(p->module->slotlist[slotidx], CKF_RW_SESSION|
+ 	    CKF_SERIAL_SESSION, NULL, NULL, &session)) != CKR_OK) {
+-		error("C_OpenSession failed: %lu", rv);
++		error("C_OpenSession failed for slot %lu: %lu", slotidx, rv);
+ 		return (-1);
+ 	}
+ 	if (login_required && pin != NULL && strlen(pin) != 0) {
+@@ -854,7 +994,8 @@ static struct sshkey *
+ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+     CK_OBJECT_HANDLE *obj)
+ {
+-	CK_ATTRIBUTE		 key_attr[3];
++	CK_ATTRIBUTE		 key_attr[4];
++	int			 nattr = 4;
+ 	CK_SESSION_HANDLE	 session;
+ 	CK_FUNCTION_LIST	*f = NULL;
+ 	CK_RV			 rv;
+@@ -867,14 +1008,15 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 
+ 	memset(&key_attr, 0, sizeof(key_attr));
+ 	key_attr[0].type = CKA_ID;
+-	key_attr[1].type = CKA_EC_POINT;
+-	key_attr[2].type = CKA_EC_PARAMS;
++	key_attr[1].type = CKA_LABEL;
++	key_attr[2].type = CKA_EC_POINT;
++	key_attr[3].type = CKA_EC_PARAMS;
+ 
+-	session = p->slotinfo[slotidx].session;
+-	f = p->function_list;
++	session = p->module->slotinfo[slotidx].session;
++	f = p->module->function_list;
+ 
+ 	/* figure out size of the attributes */
+-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_GetAttributeValue failed: %lu", rv);
+ 		return (NULL);
+@@ -885,19 +1027,19 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	 * ensure that none of the others are zero length.
+ 	 * XXX assumes CKA_ID is always first.
+ 	 */
+-	if (key_attr[1].ulValueLen == 0 ||
+-	    key_attr[2].ulValueLen == 0) {
++	if (key_attr[2].ulValueLen == 0 ||
++	    key_attr[3].ulValueLen == 0) {
+ 		error("invalid attribute length");
+ 		return (NULL);
+ 	}
+ 
+ 	/* allocate buffers for attributes */
+-	for (i = 0; i < 3; i++)
++	for (i = 0; i < nattr; i++)
+ 		if (key_attr[i].ulValueLen > 0)
+ 			key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
+ 
+ 	/* retrieve ID, public point and curve parameters of EC key */
+-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_GetAttributeValue failed: %lu", rv);
+ 		goto fail;
+@@ -909,8 +1051,8 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 		goto fail;
+ 	}
+ 
+-	attrp = key_attr[2].pValue;
+-	group = d2i_ECPKParameters(NULL, &attrp, key_attr[2].ulValueLen);
++	attrp = key_attr[3].pValue;
++	group = d2i_ECPKParameters(NULL, &attrp, key_attr[3].ulValueLen);
+ 	if (group == NULL) {
+ 		ossl_error("d2i_ECPKParameters failed");
+ 		goto fail;
+@@ -921,13 +1063,13 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 		goto fail;
+ 	}
+ 
+-	if (key_attr[1].ulValueLen <= 2) {
++	if (key_attr[2].ulValueLen <= 2) {
+ 		error("CKA_EC_POINT too small");
+ 		goto fail;
+ 	}
+ 
+-	attrp = key_attr[1].pValue;
+-	octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[1].ulValueLen);
++	attrp = key_attr[2].pValue;
++	octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[2].ulValueLen);
+ 	if (octet == NULL) {
+ 		ossl_error("d2i_ASN1_OCTET_STRING failed");
+ 		goto fail;
+@@ -989,7 +1131,8 @@ static struct sshkey *
+ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+     CK_OBJECT_HANDLE *obj)
+ {
+-	CK_ATTRIBUTE		 key_attr[3];
++	CK_ATTRIBUTE		 key_attr[4];
++	int			 nattr = 4;
+ 	CK_SESSION_HANDLE	 session;
+ 	CK_FUNCTION_LIST	*f = NULL;
+ 	CK_RV			 rv;
+@@ -1000,14 +1143,15 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 
+ 	memset(&key_attr, 0, sizeof(key_attr));
+ 	key_attr[0].type = CKA_ID;
+-	key_attr[1].type = CKA_MODULUS;
+-	key_attr[2].type = CKA_PUBLIC_EXPONENT;
++	key_attr[1].type = CKA_LABEL;
++	key_attr[2].type = CKA_MODULUS;
++	key_attr[3].type = CKA_PUBLIC_EXPONENT;
+ 
+-	session = p->slotinfo[slotidx].session;
+-	f = p->function_list;
++	session = p->module->slotinfo[slotidx].session;
++	f = p->module->function_list;
+ 
+ 	/* figure out size of the attributes */
+-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_GetAttributeValue failed: %lu", rv);
+ 		return (NULL);
+@@ -1018,19 +1162,19 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	 * ensure that none of the others are zero length.
+ 	 * XXX assumes CKA_ID is always first.
+ 	 */
+-	if (key_attr[1].ulValueLen == 0 ||
+-	    key_attr[2].ulValueLen == 0) {
++	if (key_attr[2].ulValueLen == 0 ||
++	    key_attr[3].ulValueLen == 0) {
+ 		error("invalid attribute length");
+ 		return (NULL);
+ 	}
+ 
+ 	/* allocate buffers for attributes */
+-	for (i = 0; i < 3; i++)
++	for (i = 0; i < nattr; i++)
+ 		if (key_attr[i].ulValueLen > 0)
+ 			key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
+ 
+ 	/* retrieve ID, modulus and public exponent of RSA key */
+-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_GetAttributeValue failed: %lu", rv);
+ 		goto fail;
+@@ -1042,8 +1186,8 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 		goto fail;
+ 	}
+ 
+-	rsa_n = BN_bin2bn(key_attr[1].pValue, key_attr[1].ulValueLen, NULL);
+-	rsa_e = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
++	rsa_n = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
++	rsa_e = BN_bin2bn(key_attr[3].pValue, key_attr[3].ulValueLen, NULL);
+ 	if (rsa_n == NULL || rsa_e == NULL) {
+ 		error("BN_bin2bn failed");
+ 		goto fail;
+@@ -1075,7 +1219,7 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	/* success */
+ 	success = 0;
+ fail:
+-	for (i = 0; i < 3; i++)
++	for (i = 0; i < nattr; i++)
+ 		free(key_attr[i].pValue);
+ 	RSA_free(rsa);
+ 	if (success != 0) {
+@@ -1090,7 +1234,8 @@ static struct sshkey *
+ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+     CK_OBJECT_HANDLE *obj)
+ {
+-	CK_ATTRIBUTE		 key_attr[3];
++	CK_ATTRIBUTE		 key_attr[4];
++	int			 nattr = 4;
+ 	CK_SESSION_HANDLE	 session;
+ 	CK_FUNCTION_LIST	*f = NULL;
+ 	CK_RV			 rv;
+@@ -1110,14 +1255,15 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 
+ 	memset(&key_attr, 0, sizeof(key_attr));
+ 	key_attr[0].type = CKA_ID;
+-	key_attr[1].type = CKA_EC_POINT; /* XXX or CKA_VALUE ? */
+-	key_attr[2].type = CKA_EC_PARAMS;
++	key_attr[1].type = CKA_LABEL;
++	key_attr[2].type = CKA_EC_POINT; /* XXX or CKA_VALUE ? */
++	key_attr[3].type = CKA_EC_PARAMS;
+ 
+-	session = p->slotinfo[slotidx].session;
+-	f = p->function_list;
++	session = p->module->slotinfo[slotidx].session;
++	f = p->module->function_list;
+ 
+ 	/* figure out size of the attributes */
+-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_GetAttributeValue failed: %lu", rv);
+ 		return (NULL);
+@@ -1128,28 +1274,28 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	 * ensure that none of the others are zero length.
+ 	 * XXX assumes CKA_ID is always first.
+ 	 */
+-	if (key_attr[1].ulValueLen == 0 ||
+-	    key_attr[2].ulValueLen == 0) {
++	if (key_attr[2].ulValueLen == 0 ||
++	    key_attr[3].ulValueLen == 0) {
+ 		error("invalid attribute length");
+ 		return (NULL);
+ 	}
+ 
+ 	/* allocate buffers for attributes */
+-	for (i = 0; i < 3; i++) {
++	for (i = 0; i < nattr; i++) {
+ 		if (key_attr[i].ulValueLen > 0)
+ 			key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
+ 	}
+ 
+ 	/* retrieve ID, public point and curve parameters of EC key */
+-	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
++	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_GetAttributeValue failed: %lu", rv);
+ 		goto fail;
+ 	}
+ 
+ 	/* Expect one of the supported identifiers in CKA_EC_PARAMS */
+-	d = (u_char *)key_attr[2].pValue;
+-	len = key_attr[2].ulValueLen;
++	d = (u_char *)key_attr[3].pValue;
++	len = key_attr[3].ulValueLen;
+ 	if ((len != sizeof(id1) || memcmp(d, id1, sizeof(id1)) != 0) &&
+ 	    (len != sizeof(id2) || memcmp(d, id2, sizeof(id2)) != 0)) {
+ 		hex = tohex(d, len);
+@@ -1161,16 +1307,16 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	 * Expect either a raw 32 byte pubkey or an OCTET STRING with
+ 	 * a 32 byte pubkey in CKA_VALUE
+ 	 */
+-	d = (u_char *)key_attr[1].pValue;
+-	len = key_attr[1].ulValueLen;
++	d = (u_char *)key_attr[2].pValue;
++	len = key_attr[2].ulValueLen;
+ 	if (len == ED25519_PK_SZ + 2 && d[0] == 0x04 && d[1] == ED25519_PK_SZ) {
+ 		d += 2;
+ 		len -= 2;
+ 	}
+ 	if (len != ED25519_PK_SZ) {
+-		hex = tohex(key_attr[1].pValue, key_attr[1].ulValueLen);
++		hex = tohex(key_attr[2].pValue, key_attr[2].ulValueLen);
+ 		logit_f("CKA_EC_POINT invalid octet str: %s (len %lu)",
+-		    hex, (u_long)key_attr[1].ulValueLen);
++		    hex, (u_long)key_attr[2].ulValueLen);
+ 		goto fail;
+ 	}
+ 
+@@ -1190,7 +1336,7 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 		key = NULL;
+ 	}
+ 	free(hex);
+-	for (i = 0; i < 3; i++)
++	for (i = 0; i < nattr; i++)
+ 		free(key_attr[i].pValue);
+ 	return key;
+ }
+@@ -1200,7 +1346,8 @@ static int
+ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+     CK_OBJECT_HANDLE *obj, struct sshkey **keyp, char **labelp)
+ {
+-	CK_ATTRIBUTE		 cert_attr[3];
++	CK_ATTRIBUTE		 cert_attr[4];
++	int			 nattr = 4;
+ 	CK_SESSION_HANDLE	 session;
+ 	CK_FUNCTION_LIST	*f = NULL;
+ 	CK_RV			 rv;
+@@ -1226,14 +1373,15 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 
+ 	memset(&cert_attr, 0, sizeof(cert_attr));
+ 	cert_attr[0].type = CKA_ID;
+-	cert_attr[1].type = CKA_SUBJECT;
+-	cert_attr[2].type = CKA_VALUE;
++	cert_attr[1].type = CKA_LABEL;
++	cert_attr[2].type = CKA_SUBJECT;
++	cert_attr[3].type = CKA_VALUE;
+ 
+-	session = p->slotinfo[slotidx].session;
+-	f = p->function_list;
++	session = p->module->slotinfo[slotidx].session;
++	f = p->module->function_list;
+ 
+ 	/* figure out size of the attributes */
+-	rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
++	rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_GetAttributeValue failed: %lu", rv);
+ 		return -1;
+@@ -1245,18 +1393,19 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	 * XXX assumes CKA_ID is always first.
+ 	 */
+ 	if (cert_attr[1].ulValueLen == 0 ||
+-	    cert_attr[2].ulValueLen == 0) {
++	    cert_attr[2].ulValueLen == 0 ||
++	    cert_attr[3].ulValueLen == 0) {
+ 		error("invalid attribute length");
+ 		return -1;
+ 	}
+ 
+ 	/* allocate buffers for attributes */
+-	for (i = 0; i < 3; i++)
++	for (i = 0; i < nattr; i++)
+ 		if (cert_attr[i].ulValueLen > 0)
+ 			cert_attr[i].pValue = xcalloc(1, cert_attr[i].ulValueLen);
+ 
+ 	/* retrieve ID, subject and value of certificate */
+-	rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
++	rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_GetAttributeValue failed: %lu", rv);
+ 		goto out;
+@@ -1270,8 +1419,8 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 		subject = xstrdup("invalid subject");
+ 	X509_NAME_free(x509_name);
+ 
+-	cp = cert_attr[2].pValue;
+-	if ((x509 = d2i_X509(NULL, &cp, cert_attr[2].ulValueLen)) == NULL) {
++	cp = cert_attr[3].pValue;
++	if ((x509 = d2i_X509(NULL, &cp, cert_attr[3].ulValueLen)) == NULL) {
+ 		error("d2i_x509 failed");
+ 		goto out;
+ 	}
+@@ -1381,7 +1530,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 		goto out;
+ 	}
+  out:
+-	for (i = 0; i < 3; i++)
++	for (i = 0; i < nattr; i++)
+ 		free(cert_attr[i].pValue);
+ 	X509_free(x509);
+ 	RSA_free(rsa);
+@@ -1424,11 +1573,12 @@ note_key(struct pkcs11_provider *p, CK_ULONG slotidx, const char *context,
+  */
+ static int
+ pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
+-    struct sshkey ***keysp, char ***labelsp, int *nkeys)
++    struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
+ {
+ 	struct sshkey		*key = NULL;
+ 	CK_OBJECT_CLASS		 key_class;
+-	CK_ATTRIBUTE		 key_attr[1];
++	CK_ATTRIBUTE		 key_attr[3];
++	int			 nattr = 1;
+ 	CK_SESSION_HANDLE	 session;
+ 	CK_FUNCTION_LIST	*f = NULL;
+ 	CK_RV			 rv;
+@@ -1445,10 +1595,23 @@ pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	key_attr[0].pValue = &key_class;
+ 	key_attr[0].ulValueLen = sizeof(key_class);
+ 
+-	session = p->slotinfo[slotidx].session;
+-	f = p->function_list;
++	if (uri->id != NULL) {
++		key_attr[nattr].type = CKA_ID;
++		key_attr[nattr].pValue = uri->id;
++		key_attr[nattr].ulValueLen = uri->id_len;
++		nattr++;
++	}
++	if (uri->object != NULL) {
++		key_attr[nattr].type = CKA_LABEL;
++		key_attr[nattr].pValue = uri->object;
++		key_attr[nattr].ulValueLen = strlen(uri->object);
++		nattr++;
++	}
++
++	session = p->module->slotinfo[slotidx].session;
++	f = p->module->function_list;
+ 
+-	rv = f->C_FindObjectsInit(session, key_attr, 1);
++	rv = f->C_FindObjectsInit(session, key_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_FindObjectsInit failed: %lu", rv);
+ 		goto fail;
+@@ -1521,6 +1684,13 @@ fail:
+ 
+ 	return (ret);
+ }
++#else
++static int
++pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
++    struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
++{
++	return 0;
++}
+ #endif /* WITH_OPENSSL */
+ 
+ /*
+@@ -1530,11 +1700,12 @@ fail:
+  */
+ static int
+ pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
+-    struct sshkey ***keysp, char ***labelsp, int *nkeys)
++    struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
+ {
+ 	struct sshkey		*key = NULL;
+ 	CK_OBJECT_CLASS		 key_class;
+-	CK_ATTRIBUTE		 key_attr[2];
++	CK_ATTRIBUTE		 key_attr[3];
++	int			 nattr = 1;
+ 	CK_SESSION_HANDLE	 session;
+ 	CK_FUNCTION_LIST	*f = NULL;
+ 	CK_RV			 rv;
+@@ -1550,10 +1721,23 @@ pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
+ 	key_attr[0].pValue = &key_class;
+ 	key_attr[0].ulValueLen = sizeof(key_class);
+ 
+-	session = p->slotinfo[slotidx].session;
+-	f = p->function_list;
++	if (uri->id != NULL) {
++		key_attr[nattr].type = CKA_ID;
++		key_attr[nattr].pValue = uri->id;
++		key_attr[nattr].ulValueLen = uri->id_len;
++		nattr++;
++	}
++	if (uri->object != NULL) {
++		key_attr[nattr].type = CKA_LABEL;
++		key_attr[nattr].pValue = uri->object;
++		key_attr[nattr].ulValueLen = strlen(uri->object);
++		nattr++;
++	}
++
++	session = p->module->slotinfo[slotidx].session;
++	f = p->module->function_list;
+ 
+-	rv = f->C_FindObjectsInit(session, key_attr, 1);
++	rv = f->C_FindObjectsInit(session, key_attr, nattr);
+ 	if (rv != CKR_OK) {
+ 		error("C_FindObjectsInit failed: %lu", rv);
+ 		goto fail;
+@@ -1841,16 +2025,10 @@ pkcs11_ecdsa_generate_private_key(struct pkcs11_provider *p, CK_ULONG slotidx,
+ }
+ #endif /* WITH_PKCS11_KEYGEN */
+ 
+-/*
+- * register a new provider, fails if provider already exists. if
+- * keyp is provided, fetch keys.
+- */
+ static int
+-pkcs11_register_provider(char *provider_id, char *pin,
+-    struct sshkey ***keyp, char ***labelsp,
+-    struct pkcs11_provider **providerp, CK_ULONG user)
++pkcs11_initialize_provider(struct pkcs11_uri *uri, struct pkcs11_provider **providerp)
+ {
+-	int nkeys, need_finalize = 0;
++	int need_finalize = 0;
+ 	int ret = -1;
+ 	struct pkcs11_provider *p = NULL;
+ 	void *handle = NULL;
+@@ -1859,128 +2037,126 @@ pkcs11_register_provider(char *provider_id, char *pin,
+ 	CK_FUNCTION_LIST *f = NULL;
+ 	CK_TOKEN_INFO *token;
+ 	CK_ULONG i;
++	char *provider_module = NULL;
++	struct pkcs11_module *m = NULL;
++
++	/* if no provider specified, fallback to p11-kit */
++	if (uri->module_path == NULL) {
++#ifdef PKCS11_DEFAULT_PROVIDER
++		provider_module = strdup(PKCS11_DEFAULT_PROVIDER);
++#else
++		error_f("No module path provided");
++ 		goto fail;
++#endif
++	} else {
++		provider_module = strdup(uri->module_path);
++	}
++	p = xcalloc(1, sizeof(*p));
++	p->name = pkcs11_uri_get(uri);
+ 
+-	if (providerp == NULL)
+-		goto fail;
+-	*providerp = NULL;
+-
+-	if (keyp != NULL)
+-		*keyp = NULL;
+-	if (labelsp != NULL)
+-		*labelsp = NULL;
+-
+-	if (pkcs11_provider_lookup(provider_id) != NULL) {
+-		debug_f("provider already registered: %s", provider_id);
++	if (lib_contains_symbol(provider_module, "C_GetFunctionList") != 0) {
++		error("provider %s is not a PKCS11 library", provider_module);
+ 		goto fail;
+ 	}
+-	if (lib_contains_symbol(provider_id, "C_GetFunctionList") != 0) {
+-		error("provider %s is not a PKCS11 library", provider_id);
+-		goto fail;
++	if ((m = pkcs11_provider_lookup_module(provider_module)) != NULL
++	   && m->valid) {
++		debug_f("provider module already initialized: %s", provider_module);
++		free(provider_module);
++		/* Skip the initialization of PKCS#11 module */
++		m->refcount++;
++		p->module = m;
++		p->valid = 1;
++		TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
++		p->refcount++;	/* add to provider list */
++		*providerp = p;
++		return 0;
++	} else {
++		m = xcalloc(1, sizeof(*m));
++		p->module = m;
++		m->refcount++;
+ 	}
++
+ 	/* open shared pkcs11-library */
+-	if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) {
+-		error("dlopen %s failed: %s", provider_id, dlerror());
++	if ((handle = dlopen(provider_module, RTLD_NOW)) == NULL) {
++		error("dlopen %s failed: %s", provider_module, dlerror());
+ 		goto fail;
+ 	}
+ 	if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL)
+ 		fatal("dlsym(C_GetFunctionList) failed: %s", dlerror());
+-	p = xcalloc(1, sizeof(*p));
+-	p->name = xstrdup(provider_id);
+-	p->handle = handle;
+-	/* set up the pkcs11 callbacks */
++	p->module->handle = handle;
++	/* setup the pkcs11 callbacks */
+ 	if ((rv = (*getfunctionlist)(&f)) != CKR_OK) {
+ 		error("C_GetFunctionList for provider %s failed: %lu",
+-		    provider_id, rv);
++		    provider_module, rv);
+ 		goto fail;
+ 	}
+-	p->function_list = f;
++	m->function_list = f;
+ 	if ((rv = f->C_Initialize(NULL)) != CKR_OK) {
+ 		error("C_Initialize for provider %s failed: %lu",
+-		    provider_id, rv);
++		    provider_module, rv);
+ 		goto fail;
+ 	}
+ 	need_finalize = 1;
+-	if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) {
++	if ((rv = f->C_GetInfo(&m->info)) != CKR_OK) {
+ 		error("C_GetInfo for provider %s failed: %lu",
+-		    provider_id, rv);
++		    provider_module, rv);
+ 		goto fail;
+ 	}
+-	debug("provider %s: manufacturerID <%.*s> cryptokiVersion %d.%d"
+-	    " libraryDescription <%.*s> libraryVersion %d.%d",
+-	    provider_id,
+-	    RMSPACE(p->info.manufacturerID),
+-	    p->info.cryptokiVersion.major,
+-	    p->info.cryptokiVersion.minor,
+-	    RMSPACE(p->info.libraryDescription),
+-	    p->info.libraryVersion.major,
+-	    p->info.libraryVersion.minor);
+-	if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) {
++	rmspace(m->info.manufacturerID, sizeof(m->info.manufacturerID));
++	if (uri->lib_manuf != NULL &&
++	    strncmp(uri->lib_manuf, m->info.manufacturerID, 32)) {
++		debug_f("Skipping provider %s not matching library_manufacturer",
++		    m->info.manufacturerID);
++ 		goto fail;
++ 	}
++	rmspace(m->info.libraryDescription, sizeof(m->info.libraryDescription));
++	debug("provider %s: manufacturerID <%.32s> cryptokiVersion %d.%d"
++	    " libraryDescription <%.32s> libraryVersion %d.%d",
++	    provider_module,
++	    m->info.manufacturerID,
++	    m->info.cryptokiVersion.major,
++	    m->info.cryptokiVersion.minor,
++	    m->info.libraryDescription,
++	    m->info.libraryVersion.major,
++	    m->info.libraryVersion.minor);
++
++	if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &m->nslots)) != CKR_OK) {
+ 		error("C_GetSlotList failed: %lu", rv);
+ 		goto fail;
+ 	}
+-	if (p->nslots == 0) {
+-		debug_f("provider %s returned no slots", provider_id);
++	if (m->nslots == 0) {
++		debug_f("provider %s returned no slots", provider_module);
+ 		ret = -SSH_PKCS11_ERR_NO_SLOTS;
+ 		goto fail;
+ 	}
+-	p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID));
+-	if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots))
++	m->slotlist = xcalloc(m->nslots, sizeof(CK_SLOT_ID));
++	if ((rv = f->C_GetSlotList(CK_TRUE, m->slotlist, &m->nslots))
+ 	    != CKR_OK) {
+ 		error("C_GetSlotList for provider %s failed: %lu",
+-		    provider_id, rv);
++		    provider_module, rv);
+ 		goto fail;
+ 	}
+-	p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo));
++	m->slotinfo = xcalloc(m->nslots, sizeof(struct pkcs11_slotinfo));
+ 	p->valid = 1;
+-	nkeys = 0;
+-	for (i = 0; i < p->nslots; i++) {
+-		token = &p->slotinfo[i].token;
+-		if ((rv = f->C_GetTokenInfo(p->slotlist[i], token))
++	m->valid = 1;
++	for (i = 0; i < m->nslots; i++) {
++		token = &m->slotinfo[i].token;
++		if ((rv = f->C_GetTokenInfo(m->slotlist[i], token))
+ 		    != CKR_OK) {
+ 			error("C_GetTokenInfo for provider %s slot %lu "
+-			    "failed: %lu", provider_id, (u_long)i, rv);
+-			continue;
+-		}
+-		if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) {
+-			debug2_f("ignoring uninitialised token in "
+-			    "provider %s slot %lu", provider_id, (u_long)i);
++			    "failed: %lu", provider_module, (u_long)i, rv);
+ 			continue;
+ 		}
+ 		debug("provider %s slot %lu: label <%.*s> "
+ 		    "manufacturerID <%.*s> model <%.*s> serial <%.*s> "
+ 		    "flags 0x%lx",
+-		    provider_id, (unsigned long)i,
++		    provider_module, (unsigned long)i,
+ 		    RMSPACE(token->label), RMSPACE(token->manufacturerID),
+ 		    RMSPACE(token->model), RMSPACE(token->serialNumber),
+ 		    token->flags);
+-		/*
+-		 * open session, login with pin and retrieve public
+-		 * keys (if keyp is provided)
+-		 */
+-		if ((ret = pkcs11_open_session(p, i, pin, user)) != 0 ||
+-		    keyp == NULL)
+-			continue;
+-		pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys);
+-#ifdef WITH_OPENSSL
+-		pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
+-#endif
+-		if (nkeys == 0 && !p->slotinfo[i].logged_in &&
+-		    pkcs11_interactive) {
+-			/*
+-			 * Some tokens require login before they will
+-			 * expose keys.
+-			 */
+-			if (pkcs11_login_slot(p, &p->slotinfo[i],
+-			    CKU_USER) < 0) {
+-				error("login failed");
+-				continue;
+-			}
+-			pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys);
+-#ifdef WITH_OPENSSL
+-			pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
+-#endif
+-		}
+ 	}
++	m->module_path = provider_module;
++	provider_module = NULL;
+ 
+ 	/* now owned by caller */
+ 	*providerp = p;
+@@ -1988,21 +2164,22 @@ pkcs11_register_provider(char *provider_id, char *pin,
+ 	TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
+ 	p->refcount++;	/* add to provider list */
+ 
+-	return (nkeys);
++	return 0;
+ fail:
+ 	if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK)
+ 		error("C_Finalize for provider %s failed: %lu",
+-		    provider_id, rv);
++		    provider_module, rv);
++	free(provider_module);
++	if (m) {
++		free(m->slotlist);
++		free(m);
++	}
+ 	if (p) {
+ 		free(p->name);
+-		free(p->slotlist);
+-		free(p->slotinfo);
+ 		free(p);
+ 	}
+ 	if (handle)
+ 		dlclose(handle);
+-	if (ret > 0)
+-		ret = -1;
+ 	return (ret);
+ }
+ 
+@@ -2038,18 +2215,163 @@ pkcs11_terminate(void)
+ }
+ 
+ /*
+- * register a new provider and get number of keys hold by the token,
+- * fails if provider already exists
++ * register a new provider, fails if provider already exists. if
++ * keyp is provided, fetch keys.
+  */
++static int
++pkcs11_register_provider_by_uri(struct pkcs11_uri *uri, char *pin,
++    struct sshkey ***keyp, char ***labelsp, struct pkcs11_provider **providerp,
++    CK_ULONG user)
++{
++	int nkeys;
++	int ret = -1;
++	struct pkcs11_provider *p = NULL;
++	CK_ULONG i;
++	CK_TOKEN_INFO *token;
++	char *provider_uri = NULL;
++
++	if (providerp == NULL)
++		goto fail;
++	*providerp = NULL;
++
++	if (keyp != NULL)
++		*keyp = NULL;
++
++	if ((ret = pkcs11_initialize_provider(uri, &p)) != 0) {
++		goto fail;
++	}
++
++	provider_uri = pkcs11_uri_get(uri);
++	if (pin == NULL && uri->pin != NULL) {
++		pin = uri->pin;
++	}
++	nkeys = 0;
++	for (i = 0; i < p->module->nslots; i++) {
++		token = &p->module->slotinfo[i].token;
++		if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) {
++			debug2_f("ignoring uninitialised token in "
++			    "provider %s slot %lu", provider_uri, (u_long)i);
++			continue;
++		}
++		if (uri->token != NULL &&
++		    strncmp(token->label, uri->token, 32) != 0) {
++			debug2_f("ignoring token not matching label (%.32s) "
++			    "specified by PKCS#11 URI in slot %lu",
++			    token->label, (unsigned long)i);
++			continue;
++		}
++		if (uri->manuf != NULL &&
++		    strncmp(token->manufacturerID, uri->manuf, 32) != 0) {
++			debug2_f("ignoring token not matching requrested "
++			    "manufacturerID (%.32s) specified by PKCS#11 URI in "
++			    "slot %lu", token->manufacturerID, (unsigned long)i);
++			continue;
++		}
++		if (uri->serial != NULL &&
++		    strncmp(token->serialNumber, uri->serial, 16) != 0) {
++			debug2_f("ignoring token not matching requrested "
++			    "serialNumber (%s) specified by PKCS#11 URI in "
++			    "slot %lu", token->serialNumber, (unsigned long)i);
++			continue;
++		}
++		debug("provider %s slot %lu: label <%.32s> manufacturerID <%.32s> "
++		    "model <%.16s> serial <%.16s> flags 0x%lx",
++		    provider_uri, (unsigned long)i,
++		    token->label, token->manufacturerID, token->model,
++		    token->serialNumber, token->flags);
++		/*
++		 * open session if not yet opened, login with pin and
++		 * retrieve public keys (if keyp is provided)
++		 */
++		if ((p->module->slotinfo[i].session != 0 ||
++		    (ret = pkcs11_open_session(p, i, pin, user)) != 0) && /* ??? */
++		    keyp == NULL)
++			continue;
++		pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
++		pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
++		if (nkeys == 0 && !p->module->slotinfo[i].logged_in &&
++		    pkcs11_interactive) {
++			/*
++			 * Some tokens require login before they will
++			 * expose keys.
++			 */
++			debug3_f("Trying to login as there were no keys found");
++			if (pkcs11_login_slot(p, &p->module->slotinfo[i],
++			    CKU_USER) < 0) {
++				error("login failed");
++				continue;
++			}
++			pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
++			pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
++		}
++		if (nkeys == 0 && uri->object != NULL) {
++			debug3_f("No keys found. Retrying without label (%.32s) ",
++			    uri->object);
++			/* Try once more without the label filter */
++			char *label = uri->object;
++			uri->object = NULL; /* XXX clone uri? */
++			pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
++			pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
++			uri->object = label;
++		}
++	}
++	pin = NULL; /* Will be cleaned up with URI */
++
++	/* now owned by caller */
++	*providerp = p;
++
++	free(provider_uri);
++	return (nkeys);
++ fail:
++	if (p) {
++		TAILQ_REMOVE(&pkcs11_providers, p, next);
++	     	pkcs11_provider_unref(p);
++	}
++	if (ret > 0)
++		ret = -1;
++	return (ret);
++}
++
++#ifdef WITH_PKCS11_KEYGEN
++static int
++pkcs11_register_provider(char *provider_id, char *pin, struct sshkey ***keyp,
++    char ***labelsp, struct pkcs11_provider **providerp, CK_ULONG user)
++{
++	struct pkcs11_uri *uri = NULL;
++	int r;
++
++	debug_f("called, provider_id = %s", provider_id);
++
++	uri = pkcs11_uri_init();
++	if (uri == NULL)
++		fatal("failed to init PKCS#11 URI");
++
++	if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
++	    strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
++		if (pkcs11_uri_parse(provider_id, uri) != 0)
++			fatal("Failed to parse PKCS#11 URI");
++	} else {
++		uri->module_path = strdup(provider_id);
++	}
++
++	r = pkcs11_register_provider_by_uri(uri, pin, keyp, labelsp, providerp, user);
++	pkcs11_uri_cleanup(uri);
++
++	return r;
++}
++#endif
++
+ int
+-pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp,
+-    char ***labelsp)
++pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin,
++    struct sshkey ***keyp, char ***labelsp)
+ {
+ 	struct pkcs11_provider *p = NULL;
+ 	int nkeys;
++	char *provider_uri = pkcs11_uri_get(uri);
+ 
+-	nkeys = pkcs11_register_provider(provider_id, pin, keyp, labelsp,
+-	    &p, CKU_USER);
++	debug_f("called, provider_uri = %s", provider_uri);
++
++	nkeys = pkcs11_register_provider_by_uri(uri, pin, keyp, labelsp, &p, CKU_USER);
+ 
+ 	/* no keys found or some other error, de-register provider */
+ 	if (nkeys <= 0 && p != NULL) {
+@@ -2058,11 +2380,38 @@ pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp,
+ 		pkcs11_provider_unref(p);
+ 	}
+ 	if (nkeys == 0)
+-		debug_f("provider %s returned no keys", provider_id);
++		debug_f("provider %s returned no keys", provider_uri);
+ 
++	free(provider_uri);
+ 	return (nkeys);
+ }
+ 
++int
++pkcs11_add_provider(char *provider_id, char *pin,
++    struct sshkey ***keyp, char ***labelsp)
++{
++	struct pkcs11_uri *uri;
++	int nkeys;
++
++	uri = pkcs11_uri_init();
++	if (uri == NULL)
++		fatal("Failed to init PKCS#11 URI");
++
++	if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
++	    strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
++		if (pkcs11_uri_parse(provider_id, uri) != 0)
++			fatal("Failed to parse PKCS#11 URI");
++	} else {
++		uri->module_path = strdup(provider_id);
++	}
++
++	nkeys = pkcs11_add_provider_by_uri(uri, pin, keyp, labelsp);
++	pkcs11_uri_cleanup(uri);
++
++ 	return (nkeys);
++}
++
++
+ int
+ pkcs11_sign(struct sshkey *key,
+     u_char **sigp, size_t *lenp,
+diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h
+index 1d0277a6d..13459644c 100644
+--- a/ssh-pkcs11.h
++++ b/ssh-pkcs11.h
+@@ -24,12 +24,16 @@
+ #define	SSH_PKCS11_ERR_PIN_REQUIRED		4
+ #define	SSH_PKCS11_ERR_PIN_LOCKED		5
+ 
++#include "ssh-pkcs11-uri.h"
++
+ struct sshkey;
+ 
+ int	pkcs11_init(int);
+ void	pkcs11_terminate(void);
+ int	pkcs11_add_provider(char *, char *, struct sshkey ***, char ***);
++int	pkcs11_add_provider_by_uri(struct pkcs11_uri *, char *, struct sshkey ***, char ***);
+ int	pkcs11_del_provider(char *);
++int	pkcs11_uri_write(const struct sshkey *, FILE *);
+ int	pkcs11_sign(struct sshkey *, u_char **, size_t *,
+ 	    const u_char *, size_t, const char *, const char *,
+ 	    const char *, u_int);
+diff --git a/ssh.c b/ssh.c
+index c319d08af..c79d69ef0 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -844,6 +844,14 @@ main(int ac, char **av)
+ 			options.gss_deleg_creds = 1;
+ 			break;
+ 		case 'i':
++#ifdef ENABLE_PKCS11
++			if (strlen(optarg) >= strlen(PKCS11_URI_SCHEME) &&
++			    strncmp(optarg, PKCS11_URI_SCHEME,
++			    strlen(PKCS11_URI_SCHEME)) == 0) {
++				add_identity_file(&options, NULL, optarg, 1);
++				break;
++			}
++#endif
+ 			p = tilde_expand_filename(optarg, getuid());
+ 			if (stat(p, &st) == -1)
+ 				fprintf(stderr, "Warning: Identity file %s "
+@@ -1823,6 +1831,7 @@ main(int ac, char **av)
+ 
+ #ifdef ENABLE_PKCS11
+ 	(void)pkcs11_del_provider(options.pkcs11_provider);
++	pkcs11_terminate();
+ #endif
+ 
+  skip_connect:
+@@ -2334,6 +2343,45 @@ ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
+ 	    options.escape_char : SSH_ESCAPECHAR_NONE, id);
+ }
+ 
++#ifdef ENABLE_PKCS11
++static void
++load_pkcs11_identity(char *pkcs11_uri, char *identity_files[],
++    struct sshkey *identity_keys[], int *n_ids)
++{
++	int nkeys, i;
++	struct sshkey **keys;
++	struct pkcs11_uri *uri;
++
++	debug("identity file '%s' from pkcs#11", pkcs11_uri);
++	uri = pkcs11_uri_init();
++	if (uri == NULL)
++		fatal("Failed to init PKCS#11 URI");
++
++	if (pkcs11_uri_parse(pkcs11_uri, uri) != 0)
++	fatal("Failed to parse PKCS#11 URI %s", pkcs11_uri);
++
++	/* we need to merge URI and provider together */
++	if (options.pkcs11_provider != NULL && uri->module_path == NULL)
++		uri->module_path = strdup(options.pkcs11_provider);
++
++	if (options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
++	    (nkeys = pkcs11_add_provider_by_uri(uri, NULL, &keys, NULL)) > 0) {
++		for (i = 0; i < nkeys; i++) {
++			if (*n_ids >= SSH_MAX_IDENTITY_FILES) {
++				sshkey_free(keys[i]);
++				continue;
++			}
++			identity_keys[*n_ids] = keys[i];
++			identity_files[*n_ids] = pkcs11_uri_get(uri);
++			(*n_ids)++;
++		}
++		free(keys);
++	}
++
++	pkcs11_uri_cleanup(uri);
++}
++#endif /* ENABLE_PKCS11 */
++
+ /* Loads all IdentityFile and CertificateFile keys */
+ static void
+ load_public_identity_files(const struct ssh_conn_info *cinfo)
+@@ -2348,11 +2396,6 @@ load_public_identity_files(const struct ssh_conn_info *cinfo)
+ 	char *certificate_files[SSH_MAX_CERTIFICATE_FILES];
+ 	struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES];
+ 	int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES];
+-#ifdef ENABLE_PKCS11
+-	struct sshkey **keys = NULL;
+-	char **comments = NULL;
+-	int nkeys;
+-#endif /* PKCS11 */
+ 
+ 	n_ids = n_certs = 0;
+ 	memset(identity_files, 0, sizeof(identity_files));
+@@ -2365,33 +2408,46 @@ load_public_identity_files(const struct ssh_conn_info *cinfo)
+ 	    sizeof(certificate_file_userprovided));
+ 
+ #ifdef ENABLE_PKCS11
+-	if (options.pkcs11_provider != NULL &&
+-	    options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
+-	    (pkcs11_init(!options.batch_mode) == 0) &&
+-	    (nkeys = pkcs11_add_provider(options.pkcs11_provider, NULL,
+-	    &keys, &comments)) > 0) {
+-		for (i = 0; i < nkeys; i++) {
+-			if (n_ids >= SSH_MAX_IDENTITY_FILES) {
+-				sshkey_free(keys[i]);
+-				free(comments[i]);
+-				continue;
+-			}
+-			identity_keys[n_ids] = keys[i];
+-			identity_files[n_ids] = comments[i]; /* transferred */
+-			n_ids++;
+-		}
+-		free(keys);
+-		free(comments);
++	/* handle fallback from PKCS11Provider option */
++	pkcs11_init(!options.batch_mode);
++
++	if (options.pkcs11_provider != NULL) {
++		struct pkcs11_uri *uri;
++
++		uri = pkcs11_uri_init();
++		if (uri == NULL)
++			fatal("Failed to init PKCS#11 URI");
++
++		/* Construct simple PKCS#11 URI to simplify access */
++		uri->module_path = strdup(options.pkcs11_provider);
++
++		/* Add it as any other IdentityFile */
++		cp = pkcs11_uri_get(uri);
++		add_identity_file(&options, NULL, cp, 1);
++		free(cp);
++
++		pkcs11_uri_cleanup(uri);
+ 	}
+ #endif /* ENABLE_PKCS11 */
+ 	for (i = 0; i < options.num_identity_files; i++) {
++		char *name = options.identity_files[i];
+ 		if (n_ids >= SSH_MAX_IDENTITY_FILES ||
+-		    strcasecmp(options.identity_files[i], "none") == 0) {
++		    strcasecmp(name, "none") == 0) {
+ 			free(options.identity_files[i]);
+ 			options.identity_files[i] = NULL;
+ 			continue;
+ 		}
+-		cp = tilde_expand_filename(options.identity_files[i], getuid());
++#ifdef ENABLE_PKCS11
++		if (strlen(name) >= strlen(PKCS11_URI_SCHEME) &&
++		    strncmp(name, PKCS11_URI_SCHEME,
++		    strlen(PKCS11_URI_SCHEME)) == 0) {
++			load_pkcs11_identity(name, identity_files,
++			    identity_keys, &n_ids);
++			free(options.identity_files[i]);
++			continue;
++		}
++#endif /* ENABLE_PKCS11 */
++		cp = tilde_expand_filename(name, getuid());
+ 		filename = default_client_percent_dollar_expand(cp, cinfo);
+ 		free(cp);
+ 		check_load(sshkey_load_public(filename, &public, NULL),
+diff --git a/ssh_config.5 b/ssh_config.5
+index 67d52d874..12cd3f7e1 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -1301,6 +1301,21 @@ may also be used in conjunction with
+ .Cm CertificateFile
+ in order to provide any certificate also needed for authentication with
+ the identity.
++.Pp
++The authentication identity can be also specified in a form of PKCS#11 URI
++starting with a string
++.Cm pkcs11: .
++There is supported a subset of the PKCS#11 URI as defined
++in RFC 7512 (implemented path arguments
++.Cm id ,
++.Cm manufacturer ,
++.Cm object ,
++.Cm token
++and query arguments
++.Cm module-path
++and
++.Cm pin-value
++). The URI can not be in quotes.
+ .It Cm IgnoreUnknown
+ Specifies a pattern-list of unknown options to be ignored if they are
+ encountered in configuration parsing.
+diff --git a/sshkey.c b/sshkey.c
+index e3fb29e70..82ef1c8da 100644
+--- a/sshkey.c
++++ b/sshkey.c
+@@ -868,8 +868,10 @@ sshkey_free_contents(struct sshkey *k)
+ 
+ 	if (k == NULL)
+ 		return;
++#ifdef ENABLE_PKCS11
+ 	if ((k->flags & SSHKEY_FLAG_EXT) != 0)
+ 		pkcs11_key_free(k);
++#endif
+ 	if ((impl = sshkey_impl_from_type(k->type)) != NULL &&
+ 	    impl->funcs->cleanup != NULL)
+ 		impl->funcs->cleanup(k);
+@@ -2316,9 +2318,11 @@ sshkey_sign(struct sshkey *key,
+ 	if (sshkey_is_sk(key)) {
+ 		r = sshsk_sign(sk_provider, key, sigp, lenp, data,
+ 		    datalen, compat, sk_pin);
++#ifdef ENABLE_PKCS11
+ 	} else if ((key->flags & SSHKEY_FLAG_EXT) != 0) {
+ 		r = pkcs11_sign(key, sigp, lenp, data, datalen,
+ 		    alg, sk_provider, sk_pin, compat);
++#endif
+ 	} else {
+ 		if (impl->funcs->sign == NULL)
+ 			r = SSH_ERR_SIGN_ALG_UNSUPPORTED;
+-- 
+2.55.0
+

diff --git a/0053-gssapi-tests.patch b/0053-gssapi-tests.patch
new file mode 100644
index 0000000..2d61145
--- /dev/null
+++ b/0053-gssapi-tests.patch
@@ -0,0 +1,230 @@
+From 204bca10918d44cc8bb1fdd99b2bf651b7e77d4c Mon Sep 17 00:00:00 2001
+From: Dmitry Belyavskiy <beldmit@gmail.com>
+Date: Thu, 15 May 2025 13:43:28 +0200
+Subject: [PATCH 53/54] gssapi-tests
+
+---
+ regress/Makefile   |   1 +
+ regress/gss-kex.sh | 198 +++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 199 insertions(+)
+ create mode 100644 regress/gss-kex.sh
+
+diff --git a/regress/Makefile b/regress/Makefile
+index 98a653256..6d050a4de 100644
+--- a/regress/Makefile
++++ b/regress/Makefile
+@@ -127,6 +127,7 @@ INTEROP_TESTS+=	dropbear-ciphers dropbear-kex dropbear-server
+ #INTEROP_TESTS+=ssh-com ssh-com-client ssh-com-keygen ssh-com-sftp
+ 
+ EXTRA_TESTS=	agent-pkcs11
++EXTRA_TESTS+=	gss-auth gss-kex
+ #EXTRA_TESTS+= 	cipher-speed
+ 
+ USERNAME=		${LOGNAME}
+diff --git a/regress/gss-kex.sh b/regress/gss-kex.sh
+new file mode 100644
+index 000000000..63b2e178b
+--- /dev/null
++++ b/regress/gss-kex.sh
+@@ -0,0 +1,198 @@
++tid="GSS-API Key Exchange"
++
++# Skip the test if GSSAPI support is not configured
++if ! grep -E '^#define GSSAPI' "$BUILDDIR/config.h" >/dev/null 2>&1; then
++    skip "GSSAPI not enabled"
++fi
++
++# We test with MIT Kerberos KDC, skip if not installed
++if ! which krb5kdc >/dev/null 2>&1; then
++    skip "MIT Kerberos KDC not installed"
++fi
++
++# The test needs nss_wrapper to emulate gethostname() and /etc/hosts,
++# we skip if the shared library is not installed
++nss_wrapper="libnss_wrapper.so"
++if ! ldconfig -p | grep "$nss_wrapper" >/dev/null 2>&1; then
++    skip "$nss_wrapper not installed"
++fi
++
++# Set up the username of the SSH client
++client="$LOGNAME"
++if [ "x$client" = "x" ]; then
++	client="$(whoami)"
++fi
++
++# Set up SSHD and KDC hostnames and resolve both to localhost
++sshd_hostname="sshd.example.org"
++kdc_hostname="kdc.example.org"
++kdc_port=2088
++hosts="$OBJ/hosts"
++echo "127.0.0.1 $sshd_hostname $kdc_hostname" > "$hosts"
++
++# Set up a directory to store Kerberos data
++gssdir="$OBJ/gss"
++mkdir -p "$gssdir"
++export KRB5CCNAME="$gssdir/cc"
++export KRB5_CONFIG="$gssdir/krb5.conf"
++export KRB5_KDC_PROFILE="$gssdir/kdc.conf"
++export KRB5_KTNAME="$gssdir/ssh.keytab"
++export KRB5RCACHETYPE="none"
++kdc_pidfile="$gssdir/pid"
++
++# Configure Kerberos
++cat<<EOF > "$KRB5_KDC_PROFILE"
++[realms]
++    EXAMPLE.ORG = {
++        database_name = $gssdir/principal
++        key_stash_file = $gssdir/stash
++        kdc_listen = $kdc_hostname:$kdc_port
++        kdc_tcp_listen = $kdc_hostname:$kdc_port
++    }
++[logging]
++    kdc = FILE:$gssdir/kdc.log
++    debug = true
++EOF
++
++cat<<EOF > "$KRB5_CONFIG"
++[libdefaults]
++    default_realm = EXAMPLE.ORG
++[realms]
++    EXAMPLE.ORG = {
++        kdc = $kdc_hostname:$kdc_port
++    }
++EOF
++
++# Back up the default sshd_config
++cp "$OBJ/sshd_config" "$OBJ/sshd_config.orig"
++
++setup_sshd() {
++    kex_alg="$1"
++
++    cp "$OBJ/sshd_config.orig" "$OBJ/sshd_config"
++
++    cat<<EOF >> "$OBJ/sshd_config"
++PubkeyAuthentication no
++PasswordAuthentication no
++GSSAPIAuthentication yes
++GSSAPIKeyExchange yes
++GSSAPIKexAlgorithms $kex_alg
++GSSAPIStrictAcceptorCheck no
++EOF
++
++    test_ssh_sshd_env_backup="$TEST_SSH_SSHD_ENV"
++    TEST_SSH_SSHD_ENV="$TEST_SSH_SSHD_ENV                  \
++                       LD_PRELOAD=$nss_wrapper             \
++                       NSS_WRAPPER_HOSTS=$hosts            \
++                       NSS_WRAPPER_HOSTNAME=$sshd_hostname \
++                       KRB5_CONFIG=$KRB5_CONFIG            \
++                       KRB5_KDC_PROFILE=$KRB5_KDC_PROFILE  \
++                       KRB5CCNAME=$KRB5CCNAME              \
++                       KRB5_KTNAME=$KRB5_KTNAME            \
++                       KRB5RCACHETYPE=$KRB5RCACHETYPE"
++    start_sshd
++}
++
++teardown_sshd() {
++    TEST_SSH_SSHD_ENV="$test_ssh_sshd_env_backup"
++    stop_sshd
++}
++
++setup_kdc() {
++    kdb5_util create -P "foo" -s
++    krb5kdc -w 1 -P "$kdc_pidfile"
++    i=0;
++    while [ ! -f "$kdc_pidfile" -a $i -lt 10 ]; do
++        i=$((i + 1))
++        sleep 1
++    done
++    test -f "$kdc_pidfile" || fatal "KDC failed to start"
++}
++
++teardown_kdc() {
++    kill "$(cat "$kdc_pidfile")"
++    kdestroy
++    rm -f "$KRB5_KTNAME" "$kdc_pidfile"
++    kdb5_util destroy -f
++}
++
++setup_nss_emulation() {
++    export LD_PRELOAD="$nss_wrapper"
++    export NSS_WRAPPER_HOSTS="$hosts"
++}
++
++teardown_nss_emulation() {
++    unset LD_PRELOAD
++    unset NSS_WRAPPER_HOSTS
++}
++
++test_gss_kex() {
++    kex_family="$1"
++    auth_sshd="$2"
++    expect="$3"
++
++    # Check if the algorithm family is recognized by ssh
++    if ! ${REAL_SSH} -G -F "$OBJ/ssh_config" \
++        -o "GSSAPIKeyExchange yes" \
++        -o "GSSAPIKexAlgorithms $kex_family" \
++        "$client@$sshd_hostname" >/dev/null 2>&1; then
++        verbose "gss kex $kex_family not supported, skipping"
++        return 0
++    fi
++
++    setup_sshd "$kex_family"
++    setup_nss_emulation
++    setup_kdc
++
++    kadmin.local add_principal -randkey "host/$sshd_hostname"
++    if $auth_sshd; then
++        kadmin.local ktadd "host/$sshd_hostname"
++    fi
++    kadmin.local add_principal -pw "foo" "$client"
++    echo "foo" | kinit "$client"
++
++    ${SSH} -F "$OBJ/ssh_config" \
++        -o "GSSAPIAuthentication yes" \
++        -o "GSSAPIKeyExchange yes" \
++        -o "GSSAPIKexAlgorithms $kex_family" \
++        -o "GSSAPIDelegateCredentials no" \
++        "$client@$sshd_hostname" true
++    status=$?
++
++    teardown_kdc
++    teardown_nss_emulation
++    teardown_sshd
++
++    [ $status -eq $expect ]
++}
++
++# GSS kex family prefixes to test.
++# GSSAPIKexAlgorithms accepts these prefixes; the full algorithm names
++# (prefix + Base64(MD5(OID))) are constructed during negotiation.
++gss_kex_families="
++    gss-group14-sha256-
++    gss-group16-sha512-
++    gss-nistp256-sha256-
++    gss-curve25519-sha256-
++    gss-group14-sha1-
++    gss-gex-sha1-
++"
++
++# Positive tests: each GSS kex algorithm connects successfully
++for kex_family in $gss_kex_families; do
++    verbose "gss kex $kex_family"
++    test_gss_kex "$kex_family" true 0 \
++        || fail "gss kex $kex_family failed"
++done
++
++# Negative test: connection must fail when keytab is missing
++verbose "gss kex negative test: no keytab"
++test_gss_kex "gss-group14-sha256-" false 255 \
++    || fail "gss kex succeeded without keytab"
++
++unset KRB5CCNAME
++unset KRB5_CONFIG
++unset KRB5_KDC_PROFILE
++unset KRB5_KTNAME
++unset KRB5RCACHETYPE
++rm -rf "$gssdir"
+-- 
+2.55.0
+

diff --git a/0053-openssh-10.2p1-pkcs11-uri.patch b/0053-openssh-10.2p1-pkcs11-uri.patch
deleted file mode 100644
index cbaeaf3..0000000
--- a/0053-openssh-10.2p1-pkcs11-uri.patch
+++ /dev/null
@@ -1,3410 +0,0 @@
-From 50a4a4adde0ff965afe34dbc059996d86d4fcfc6 Mon Sep 17 00:00:00 2001
-From: Dmitry Belyavskiy <beldmit@gmail.com>
-Date: Mon, 15 Dec 2025 14:24:07 +0100
-Subject: [PATCH 53/54] openssh-10.2p1-pkcs11-uri
-
----
- Makefile.in                      |  26 +-
- configure.ac                     |  37 ++
- regress/Makefile                 |   5 +-
- regress/pkcs11-uri.sh            | 410 ++++++++++++++++
- regress/unittests/Makefile       |   2 +-
- regress/unittests/pkcs11/tests.c | 353 ++++++++++++++
- ssh-add.c                        |  48 +-
- ssh-agent.c                      | 117 ++++-
- ssh-keygen.c                     |   7 +-
- ssh-pkcs11-client.c              |  19 +
- ssh-pkcs11-uri.c                 | 438 ++++++++++++++++++
- ssh-pkcs11-uri.h                 |  46 ++
- ssh-pkcs11.c                     | 771 ++++++++++++++++++++++---------
- ssh-pkcs11.h                     |   4 +
- ssh.c                            | 104 ++++-
- ssh_config.5                     |  15 +
- sshkey.c                         |   4 +
- 17 files changed, 2133 insertions(+), 273 deletions(-)
- create mode 100644 regress/pkcs11-uri.sh
- create mode 100644 regress/unittests/pkcs11/tests.c
- create mode 100644 ssh-pkcs11-uri.c
- create mode 100644 ssh-pkcs11-uri.h
-
-diff --git a/Makefile.in b/Makefile.in
-index f80c95846..c3c8da863 100644
---- a/Makefile.in
-+++ b/Makefile.in
-@@ -112,12 +112,12 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
- 	sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \
- 	sshbuf-io.o misc-agent.o ssherr-libcrypto.o auditstub.o
- 
--P11OBJS= ssh-pkcs11-client.o
-+P11OBJS= ssh-pkcs11-client.o ssh-pkcs11-uri.o
- 
- SKOBJS=	ssh-sk-client.o
- 
- SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
--	sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o $(SKOBJS)
-+	sshconnect.o sshconnect2.o mux.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
- 
- SSHDOBJS=sshd.o \
- 	platform-listen.o \
-@@ -161,11 +161,11 @@ SSHADD_OBJS=	ssh-add.o $(P11OBJS) $(SKOBJS)
- 
- SSHAGENT_OBJS=	ssh-agent.o $(P11OBJS) $(SKOBJS)
- 
--SSHKEYGEN_OBJS=	ssh-keygen.o sshsig.o ssh-pkcs11.o $(SKOBJS)
-+SSHKEYGEN_OBJS=	ssh-keygen.o sshsig.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
- 
- SSHKEYSIGN_OBJS=ssh-keysign.o readconf.o uidswap.o $(P11OBJS) $(SKOBJS)
- 
--P11HELPER_OBJS=	ssh-pkcs11-helper.o ssh-pkcs11.o $(SKOBJS)
-+P11HELPER_OBJS=	ssh-pkcs11-helper.o ssh-pkcs11.o ssh-pkcs11-uri.o $(SKOBJS)
- 
- SKHELPER_OBJS=	ssh-sk-helper.o ssh-sk.o sk-usbhid.o ssherr-nolibcrypto.o
- 
-@@ -331,6 +331,8 @@ clean:	regressclean
- 	rm -f regress/unittests/sshsig/test_sshsig$(EXEEXT)
- 	rm -f regress/unittests/utf8/*.o
- 	rm -f regress/unittests/utf8/test_utf8$(EXEEXT)
-+	rm -f regress/unittests/pkcs11/*.o
-+	rm -f regress/unittests/pkcs11/test_pkcs11$(EXEEXT)
- 	rm -f regress/misc/sk-dummy/*.o
- 	rm -f regress/misc/sk-dummy/*.lo
- 	rm -f regress/misc/ssh-verify-attestation/ssh-verify-attestation$(EXEEXT)
-@@ -370,6 +372,8 @@ distclean:	regressclean
- 	rm -f regress/unittests/sshsig/test_sshsig
- 	rm -f regress/unittests/utf8/*.o
- 	rm -f regress/unittests/utf8/test_utf8
-+	rm -f regress/unittests/pkcs11/*.o
-+	rm -f regress/unittests/pkcs11/test_pkcs11
- 	rm -f regress/misc/sk-dummy/*.o
- 	rm -f regress/misc/sk-dummy/*.lo
- 	rm -f regress/misc/sk-dummy/sk-dummy.so
-@@ -550,6 +554,7 @@ regress-prep:
- 	$(MKDIR_P) `pwd`/regress/unittests/sshkey
- 	$(MKDIR_P) `pwd`/regress/unittests/sshsig
- 	$(MKDIR_P) `pwd`/regress/unittests/utf8
-+	$(MKDIR_P) `pwd`/regress/unittests/pkcs11
- 	$(MKDIR_P) `pwd`/regress/misc/sk-dummy
- 	$(MKDIR_P) `pwd`/regress/misc/ssh-verify-attestation
- 	[ -f `pwd`/regress/Makefile ] || \
-@@ -725,6 +730,16 @@ regress/unittests/utf8/test_utf8$(EXEEXT): \
- 	    regress/unittests/test_helper/libtest_helper.a \
- 	    -lssh -lopenbsd-compat -lssh -lopenbsd-compat $(TESTLIBS)
- 
-+UNITTESTS_TEST_PKCS11_OBJS=\
-+	regress/unittests/pkcs11/tests.o
-+
-+regress/unittests/pkcs11/test_pkcs11$(EXEEXT): \
-+    ${UNITTESTS_TEST_PKCS11_OBJS} ssh-pkcs11-uri.o \
-+    regress/unittests/test_helper/libtest_helper.a libssh.a
-+	$(LD) -o $@ $(LDFLAGS) $(UNITTESTS_TEST_PKCS11_OBJS) \
-+	    regress/unittests/test_helper/libtest_helper.a \
-+	    ssh-pkcs11-uri.o -lssh -lopenbsd-compat -lcrypto $(LIBS) -lm
-+
- # These all need to be compiled -fPIC, so they are treated differently.
- SK_DUMMY_OBJS=\
- 	regress/misc/sk-dummy/sk-dummy.lo \
-@@ -770,7 +785,8 @@ regress-unit-binaries: regress-prep $(REGRESSLIBS) \
- 	regress/unittests/sshbuf/test_sshbuf$(EXEEXT) \
- 	regress/unittests/sshkey/test_sshkey$(EXEEXT) \
- 	regress/unittests/sshsig/test_sshsig$(EXEEXT) \
--	regress/unittests/utf8/test_utf8$(EXEEXT)
-+	regress/unittests/utf8/test_utf8$(EXEEXT) \
-+	regress/unittests/pkcs11/test_pkcs11$(EXEEXT) \
- 
- tests:	file-tests t-exec interop-tests extra-tests unit
- 	echo all tests passed
-diff --git a/configure.ac b/configure.ac
-index 62b2bf9e7..f581a9801 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -2305,12 +2305,14 @@ AC_LINK_IFELSE(
- 	[AC_DEFINE([HAVE_ISBLANK], [1], [Define if you have isblank(3C).])
- ])
- 
-+SCARD_MSG="yes"
- disable_pkcs11=
- AC_ARG_ENABLE([pkcs11],
- 	[  --disable-pkcs11        disable PKCS#11 support code [no]],
- 	[
- 		if test "x$enableval" = "xno" ; then
- 			disable_pkcs11=1
-+			SCARD_MSG="no"
- 		fi
- 	]
- )
-@@ -2340,6 +2342,40 @@ AC_SEARCH_LIBS([dlopen], [dl])
- AC_CHECK_FUNCS([dlopen])
- AC_CHECK_DECL([RTLD_NOW], [], [], [#include <dlfcn.h>])
- 
-+# Check whether we have a p11-kit, we got default provider on command line
-+DEFAULT_PKCS11_PROVIDER_MSG="no"
-+AC_ARG_WITH([default-pkcs11-provider],
-+	[  --with-default-pkcs11-provider[[=PATH]]   Use default pkcs11 provider (p11-kit detected by default)],
-+	[ if test "x$withval" != "xno" && test "x$disable_pkcs11" = "x"; then
-+		if test "x$withval" = "xyes" ; then
-+			AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no])
-+			if test "x$PKGCONFIG" != "xno"; then
-+				AC_MSG_CHECKING([if $PKGCONFIG knows about p11-kit])
-+				if "$PKGCONFIG" "p11-kit-1"; then
-+					AC_MSG_RESULT([yes])
-+					use_pkgconfig_for_p11kit=yes
-+				else
-+					AC_MSG_RESULT([no])
-+				fi
-+			fi
-+		else
-+			PKCS11_PATH="${withval}"
-+		fi
-+		if test "x$use_pkgconfig_for_p11kit" = "xyes"; then
-+			PKCS11_PATH=`$PKGCONFIG --variable=proxy_module p11-kit-1`
-+		fi
-+		AC_CHECK_FILE("$PKCS11_PATH",
-+			[ AC_DEFINE_UNQUOTED([PKCS11_DEFAULT_PROVIDER], ["$PKCS11_PATH"], [Path to default PKCS#11 provider (p11-kit proxy)])
-+			  DEFAULT_PKCS11_PROVIDER_MSG="$PKCS11_PATH"
-+			],
-+			[ AC_MSG_ERROR([Requested PKCS11 provided not found]) ]
-+		)
-+	else
-+		AC_MSG_WARN([Needs PKCS11 support to enable default pkcs11 provider])
-+	fi ]
-+)
-+
-+
- # IRIX has a const char return value for gai_strerror()
- AC_CHECK_FUNCS([gai_strerror], [
- 	AC_DEFINE([HAVE_GAI_STRERROR])
-@@ -5991,6 +6027,7 @@ echo "                  BSD Auth support: $BSD_AUTH_MSG"
- echo "              Random number source: $RAND_MSG"
- echo "             Privsep sandbox style: $SANDBOX_STYLE"
- echo "                   PKCS#11 support: $enable_pkcs11"
-+echo "          Default PKCS#11 provider: $DEFAULT_PKCS11_PROVIDER_MSG"
- echo "                  U2F/FIDO support: $enable_sk"
- 
- echo ""
-diff --git a/regress/Makefile b/regress/Makefile
-index ae45bd463..cbe7a9610 100644
---- a/regress/Makefile
-+++ b/regress/Makefile
-@@ -118,6 +118,7 @@ LTESTS= 	connect \
- 		penalty-expire \
- 		connect-bigconf \
- 		ssh-pkcs11 \
-+		pkcs11-uri \
- 		ssh-tty \
- 		proxyjump
- 
-@@ -144,7 +145,8 @@ CLEANFILES=	*.core actual agent-key.* authorized_keys_${USERNAME} \
- 		known_hosts known_hosts-cert known_hosts.* krl-* ls.copy \
- 		modpipe netcat no_identity_config \
- 		pidfile putty.rsa2 ready regress.log remote_pid \
--		revoked-* rsa rsa-agent rsa-agent.pub rsa.pub rsa_ssh2_cr.prv \
-+		revoked-* rsa rsa-agent rsa-agent.pub rsa-agent-cert.pub \
-+		rsa.pub rsa_ssh2_cr.prv pkcs11*.crt pkcs11*.key pkcs11.info \
- 		rsa_ssh2_crnl.prv scp-ssh-wrapper.exe \
- 		scp-ssh-wrapper.scp setuid-allowed sftp-server.log \
- 		sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \
-@@ -294,6 +296,7 @@ unit unit-bench:
- 		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/pkcs11/test_pkcs11 ; \
- 		 $$V ${.OBJDIR}/unittests/sshbuf/test_sshbuf $${ARGS}; \
- 		 $$V ${.OBJDIR}/unittests/sshkey/test_sshkey \
- 			-d ${.CURDIR}/unittests/sshkey/testdata $${ARGS}; \
-diff --git a/regress/pkcs11-uri.sh b/regress/pkcs11-uri.sh
-new file mode 100644
-index 000000000..72d69bda0
---- /dev/null
-+++ b/regress/pkcs11-uri.sh
-@@ -0,0 +1,410 @@
-+#
-+#  Copyright (c) 2017 Red Hat
-+#
-+#  Authors: Jakub Jelen <jjelen@redhat.com>
-+#
-+#  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.
-+
-+tid="pkcs11 tests with soft token"
-+
-+try_token_libs() {
-+	for _lib in "$@" ; do
-+		if test -f "$_lib" ; then
-+			verbose "Using token library $_lib"
-+			TEST_SSH_PKCS11="$_lib"
-+			return
-+		fi
-+	done
-+	echo "skipped: Unable to find PKCS#11 token library"
-+	exit 0
-+}
-+
-+try_token_libs \
-+	/usr/local/lib/softhsm/libsofthsm2.so \
-+	/usr/lib64/pkcs11/libsofthsm2.so \
-+	/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so
-+
-+TEST_SSH_PIN=1234
-+TEST_SSH_SOPIN=12345678
-+if [ "x$TEST_SSH_SSHPKCS11HELPER" != "x" ]; then
-+	SSH_PKCS11_HELPER="${TEST_SSH_SSHPKCS11HELPER}"
-+	export SSH_PKCS11_HELPER
-+fi
-+
-+test -f "$TEST_SSH_PKCS11" || fatal "$TEST_SSH_PKCS11 does not exist"
-+
-+# setup environment for softhsm token
-+DIR=$OBJ/SOFTHSM
-+rm -rf $DIR
-+TOKEN=$DIR/tokendir
-+mkdir -p $TOKEN
-+SOFTHSM2_CONF=$DIR/softhsm2.conf
-+export SOFTHSM2_CONF
-+cat > $SOFTHSM2_CONF << EOF
-+# SoftHSM v2 configuration file
-+directories.tokendir = ${TOKEN}
-+objectstore.backend = file
-+# ERROR, WARNING, INFO, DEBUG
-+log.level = DEBUG
-+# If CKF_REMOVABLE_DEVICE flag should be set
-+slots.removable = false
-+EOF
-+out=$(softhsm2-util --init-token --free --label token-slot-0 --pin "$TEST_SSH_PIN" --so-pin "$TEST_SSH_SOPIN")
-+slot=$(echo -- $out | sed 's/.* //')
-+
-+# prevent ssh-agent from calling ssh-askpass
-+SSH_ASKPASS=/usr/bin/true
-+export SSH_ASKPASS
-+unset DISPLAY
-+# We need interactive access to test PKCS# since it prompts for PIN
-+# Backup ssh_proxy before modifications
-+cp $OBJ/ssh_proxy $OBJ/ssh_proxy_bak
-+sed -i 's/.*BatchMode.*//g' $OBJ/ssh_proxy
-+# Remove IdentityFile entries to prevent exceeding MaxAuthTries when using
-+# PKCS11Provider (-I) or PKCS11 URIs. The default ssh_config includes multiple
-+# identity files that would be tried before PKCS11 keys, causing authentication
-+# to fail with "Too many authentication failures" before PKCS11 keys are reached.
-+grep -iv IdentityFile $OBJ/ssh_proxy > $OBJ/ssh_proxy.tmp
-+mv $OBJ/ssh_proxy.tmp $OBJ/ssh_proxy
-+
-+# start command w/o tty, so ssh accepts pin from stdin (from agent-pkcs11.sh)
-+notty() {
-+	perl -e 'use POSIX; POSIX::setsid();
-+	    if (fork) { wait; exit($? >> 8); } else { exec(@ARGV) }' "$@"
-+}
-+
-+trace "generating keys"
-+ID1="02"
-+ID2="04"
-+ID3="06"
-+RSA=${DIR}/RSA
-+EC=${DIR}/EC
-+ED25519=${DIR}/ED25519
-+openssl genpkey -algorithm rsa > $RSA
-+openssl pkcs8 -nocrypt -in $RSA |\
-+    softhsm2-util --slot "$slot" --label "SSH RSA Key $ID1" --id $ID1 \
-+	--pin "$TEST_SSH_PIN" --import /dev/stdin
-+openssl genpkey \
-+    -genparam \
-+    -algorithm ec \
-+    -pkeyopt ec_paramgen_curve:prime256v1 |\
-+    openssl genpkey \
-+    -paramfile /dev/stdin > $EC
-+openssl pkcs8 -nocrypt -in $EC |\
-+    softhsm2-util --slot "$slot" --label "SSH ECDSA Key $ID2" --id $ID2 \
-+	--pin "$TEST_SSH_PIN" --import /dev/stdin
-+openssl genpkey -algorithm ed25519 > $ED25519
-+openssl pkcs8 -nocrypt -in $ED25519 |\
-+    softhsm2-util --slot "$slot" --label "SSH ED25519 Key $ID3" --id $ID3 \
-+	--pin "$TEST_SSH_PIN" --import /dev/stdin
-+
-+trace "List the keys in the ssh-keygen with PKCS#11 URIs"
-+${SSHKEYGEN} -D ${TEST_SSH_PKCS11} > $OBJ/token_keys
-+if [ $? -ne 0 ]; then
-+	fail "FAIL: keygen fails to enumerate keys on PKCS#11 token"
-+fi
-+grep "pkcs11:" $OBJ/token_keys > /dev/null
-+if [ $? -ne 0 ]; then
-+	fail "FAIL: The keys from ssh-keygen do not contain PKCS#11 URI as a comment"
-+fi
-+
-+# Set the ECDSA key to authorized keys
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "Simple connect with ssh (without PKCS#11 URI)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -I ${TEST_SSH_PKCS11} \
-+    -F $OBJ/ssh_proxy somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with pkcs11 failed (exit code $r)"
-+fi
-+
-+trace "Connect with PKCS#11 URI"
-+trace "  (ECDSA key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+    -i "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
-+fi
-+
-+trace "  (RSA key should fail)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+     -i "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
-+fi
-+
-+# Set the ED25519 key as authorized
-+grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "  (ED25519 key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+     -i "pkcs11:id=%${ID3}?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
-+fi
-+
-+# Set the ECDSA key back as authorized
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "Connect with PKCS#11 URI including PIN should not prompt"
-+trace "  (ECDSA key should succeed)"
-+${SSH} -F $OBJ/ssh_proxy -i \
-+    "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
-+fi
-+
-+trace "  (RSA key should fail)"
-+${SSH} -F $OBJ/ssh_proxy -i \
-+    "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
-+fi
-+
-+# Set the ED25519 key as authorized
-+grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "  (ED25519 key should succeed)"
-+${SSH} -F $OBJ/ssh_proxy -i \
-+    "pkcs11:id=%${ID3}?module-path=${TEST_SSH_PKCS11}&pin-value=${TEST_SSH_PIN}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
-+fi
-+
-+# Set the ECDSA key back as authorized
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "Connect with various filtering options in PKCS#11 URI"
-+trace "  (by object label, ECDSA should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+    -i "pkcs11:object=SSH%20ECDSA%20Key%2004?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
-+fi
-+
-+trace "  (by object label, RSA key should fail)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+     -i "pkcs11:object=SSH%20RSA%20Key%2002?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
-+fi
-+
-+# Set the ED25519 key as authorized
-+grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "  (by object label, ED25519 key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+     -i "pkcs11:object=SSH%20ED25519%20Key%2006?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
-+fi
-+
-+# Set the ECDSA key back as authorized
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "  (by token label, ECDSA key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+    -i "pkcs11:id=%${ID2};token=token-slot-0?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI failed (exit code $r)"
-+fi
-+
-+# Set the ED25519 key as authorized
-+grep "ED25519" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "  (by token label, ED25519 key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+    -i "pkcs11:id=%${ID3};token=token-slot-0?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI (Ed25519) failed (exit code $r)"
-+fi
-+
-+# Set the ECDSA key back as authorized
-+grep "ECDSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "  (by wrong token label, should fail)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+     -i "pkcs11:token=token-slot-99?module-path=${TEST_SSH_PKCS11}" somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI succeeded (should fail)"
-+fi
-+
-+
-+
-+
-+trace "Test PKCS#11 URI specification in configuration files"
-+echo "IdentityFile \"pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}\"" \
-+    >> $OBJ/ssh_proxy
-+trace "  (ECDSA key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI in config failed (exit code $r)"
-+fi
-+
-+# Set the RSA key as authorized
-+grep "RSA" $OBJ/token_keys > $OBJ/authorized_keys_$USER
-+
-+trace "  (RSA key should fail)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+r=$?
-+if [ $r -eq 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI in config succeeded (should fail)"
-+fi
-+sed -i -e "/IdentityFile/d" $OBJ/ssh_proxy
-+
-+trace "Combination of PKCS11Provider and PKCS11URI on commandline"
-+trace "  (RSA key should succeed)"
-+echo ${TEST_SSH_PIN} | notty ${SSH} -F $OBJ/ssh_proxy \
-+    -i "pkcs11:id=%${ID1}" -I ${TEST_SSH_PKCS11} somehost exit 5
-+r=$?
-+if [ $r -ne 5 ]; then
-+	fail "FAIL: ssh connect with PKCS#11 URI and provider combination" \
-+	    "failed (exit code $r)"
-+fi
-+
-+trace "Regress: Missing provider in PKCS11URI option"
-+${SSH} -F $OBJ/ssh_proxy \
-+    -o IdentityFile=\"pkcs11:token=segfault\" somehost exit 5
-+r=$?
-+if [ $r -eq 139 ]; then
-+	fail "FAIL: ssh connect with missing provider_id from configuration option" \
-+	    "crashed (exit code $r)"
-+fi
-+
-+
-+trace "SSH Agent can work with PKCS#11 URI"
-+trace "start the agent"
-+eval `${SSHAGENT} -s` >  /dev/null
-+
-+r=$?
-+if [ $r -ne 0 ]; then
-+	fail "could not start ssh-agent: exit code $r"
-+else
-+	trace "add whole provider to agent"
-+	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
-+	    "pkcs11:?module-path=${TEST_SSH_PKCS11}" #> /dev/null 2>&1
-+	r=$?
-+	if [ $r -ne 0 ]; then
-+		fail "FAIL: ssh-add failed with whole provider: exit code $r"
-+	fi
-+
-+	trace " pkcs11 list via agent (all keys)"
-+	${SSHADD} -l > /dev/null 2>&1
-+	r=$?
-+	if [ $r -ne 0 ]; then
-+		fail "FAIL: ssh-add -l failed with whole provider: exit code $r"
-+	fi
-+
-+	trace " pkcs11 connect via agent (all keys)"
-+	${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+	r=$?
-+	if [ $r -ne 5 ]; then
-+		fail "FAIL: ssh connect failed with whole provider (exit code $r)"
-+	fi
-+
-+	trace " remove pkcs11 keys (all keys)"
-+	${SSHADD} -d "pkcs11:?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
-+	r=$?
-+	if [ $r -ne 0 ]; then
-+		fail "FAIL: ssh-add -d failed with whole provider: exit code $r"
-+	fi
-+
-+	trace "add only RSA key to the agent"
-+	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
-+	    "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
-+	r=$?
-+	if [ $r -ne 0 ]; then
-+		fail "FAIL ssh-add failed with RSA key: exit code $r"
-+	fi
-+
-+	trace " pkcs11 connect via agent (RSA key)"
-+	${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+	r=$?
-+	if [ $r -ne 5 ]; then
-+		fail "FAIL: ssh connect failed with RSA key (exit code $r)"
-+	fi
-+
-+	trace " remove RSA pkcs11 key"
-+	${SSHADD} -d "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" \
-+	    > /dev/null 2>&1
-+	r=$?
-+	if [ $r -ne 0 ]; then
-+		fail "FAIL: ssh-add -d failed with RSA key: exit code $r"
-+	fi
-+
-+	trace "add only ECDSA key to the agent"
-+	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
-+	    "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
-+	r=$?
-+	if [ $r -ne 0 ]; then
-+		fail "FAIL: ssh-add failed with second key: exit code $r"
-+	fi
-+
-+	trace " pkcs11 connect via agent (ECDSA key should fail)"
-+	${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+	r=$?
-+	if [ $r -eq 5 ]; then
-+		fail "FAIL: ssh connect passed with ECDSA key (should fail)"
-+	fi
-+
-+	trace "add also the RSA key to the agent"
-+	echo ${TEST_SSH_PIN} | notty ${SSHADD} \
-+	    "pkcs11:id=%${ID1}?module-path=${TEST_SSH_PKCS11}" > /dev/null 2>&1
-+	r=$?
-+	if [ $r -ne 0 ]; then
-+		fail "FAIL: ssh-add failed with first key: exit code $r"
-+	fi
-+
-+	trace " remove ECDSA pkcs11 key"
-+	${SSHADD} -d "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" \
-+	    > /dev/null 2>&1
-+	r=$?
-+	if [ $r -ne 0 ]; then
-+		fail "ssh-add -d failed with ECDSA key: exit code $r"
-+	fi
-+
-+	trace " remove already-removed pkcs11 key should fail"
-+	${SSHADD} -d "pkcs11:id=%${ID2}?module-path=${TEST_SSH_PKCS11}" \
-+	    > /dev/null 2>&1
-+	r=$?
-+	if [ $r -eq 0 ]; then
-+		fail "FAIL: ssh-add -d passed with non-existing key (should fail)"
-+	fi
-+
-+	trace " pkcs11 connect via agent (the RSA key should be still usable)"
-+	${SSH} -F $OBJ/ssh_proxy somehost exit 5
-+	r=$?
-+	if [ $r -ne 5 ]; then
-+		fail "ssh connect failed with RSA key (after removing ECDSA): exit code $r"
-+	fi
-+
-+	trace "kill agent"
-+	${SSHAGENT} -k > /dev/null
-+fi
-+
-+# Restore original ssh_proxy
-+mv $OBJ/ssh_proxy_bak $OBJ/ssh_proxy
-diff --git a/regress/unittests/Makefile b/regress/unittests/Makefile
-index e370900e4..d6c89c129 100644
---- a/regress/unittests/Makefile
-+++ b/regress/unittests/Makefile
-@@ -1,6 +1,6 @@
- #	$OpenBSD: Makefile,v 1.13 2023/09/24 08:14:13 claudio Exp $
- 
- SUBDIR=	test_helper sshbuf sshkey bitmap kex hostkeys utf8 match conversion
--SUBDIR+=authopt misc sshsig
-+SUBDIR+=authopt misc sshsig pkcs11
- 
- .include <bsd.subdir.mk>
-diff --git a/regress/unittests/pkcs11/tests.c b/regress/unittests/pkcs11/tests.c
-new file mode 100644
-index 000000000..89ba45c4e
---- /dev/null
-+++ b/regress/unittests/pkcs11/tests.c
-@@ -0,0 +1,353 @@
-+/*
-+ * Copyright (c) 2017 Red Hat
-+ *
-+ * Authors: Jakub Jelen <jjelen@redhat.com>
-+ *
-+ * 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 "includes.h"
-+
-+#include <locale.h>
-+#include <string.h>
-+
-+#include "../test_helper/test_helper.h"
-+
-+#include "sshbuf.h"
-+#include "ssh-pkcs11-uri.h"
-+
-+#define EMPTY_URI compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
-+
-+/* prototypes are not public -- specify them here internally for tests */
-+struct sshbuf *percent_encode(const char *, size_t, char *);
-+int percent_decode(char *, char **);
-+
-+void
-+compare_uri(struct pkcs11_uri *a, struct pkcs11_uri *b)
-+{
-+	ASSERT_PTR_NE(a, NULL);
-+	ASSERT_PTR_NE(b, NULL);
-+	ASSERT_SIZE_T_EQ(a->id_len, b->id_len);
-+	ASSERT_MEM_EQ(a->id, b->id, a->id_len);
-+	if (b->object != NULL)
-+		ASSERT_STRING_EQ(a->object, b->object);
-+	else /* both should be null */
-+		ASSERT_PTR_EQ(a->object, b->object);
-+	if (b->module_path != NULL)
-+		ASSERT_STRING_EQ(a->module_path, b->module_path);
-+	else /* both should be null */
-+		ASSERT_PTR_EQ(a->module_path, b->module_path);
-+	if (b->token != NULL)
-+		ASSERT_STRING_EQ(a->token, b->token);
-+	else /* both should be null */
-+		ASSERT_PTR_EQ(a->token, b->token);
-+	if (b->manuf != NULL)
-+		ASSERT_STRING_EQ(a->manuf, b->manuf);
-+	else /* both should be null */
-+		ASSERT_PTR_EQ(a->manuf, b->manuf);
-+	if (b->lib_manuf != NULL)
-+		ASSERT_STRING_EQ(a->lib_manuf, b->lib_manuf);
-+	else /* both should be null */
-+		ASSERT_PTR_EQ(a->lib_manuf, b->lib_manuf);
-+	if (b->serial != NULL)
-+		ASSERT_STRING_EQ(a->serial, b->serial);
-+	else /* both should be null */
-+		ASSERT_PTR_EQ(a->serial, b->serial);
-+}
-+
-+void
-+check_parse_rv(char *uri, struct pkcs11_uri *expect, int expect_rv)
-+{
-+	char *buf = NULL, *str;
-+	struct pkcs11_uri *pkcs11uri = NULL;
-+	int rv;
-+
-+	if (expect_rv == 0)
-+		str = "Valid";
-+	else
-+		str = "Invalid";
-+	asprintf(&buf, "%s PKCS#11 URI parsing: %s", str, uri);
-+	TEST_START(buf);
-+	free(buf);
-+	pkcs11uri = pkcs11_uri_init();
-+	rv = pkcs11_uri_parse(uri, pkcs11uri);
-+	ASSERT_INT_EQ(rv, expect_rv);
-+	if (rv == 0) /* in case of failure result is undefined */
-+		compare_uri(pkcs11uri, expect);
-+	pkcs11_uri_cleanup(pkcs11uri);
-+	free(expect);
-+	TEST_DONE();
-+}
-+
-+void
-+check_parse(char *uri, struct pkcs11_uri *expect)
-+{
-+	check_parse_rv(uri, expect, 0);
-+}
-+
-+struct pkcs11_uri *
-+compose_uri(unsigned char *id, size_t id_len, char *token, char *lib_manuf,
-+    char *manuf, char *serial, char *module_path, char *object, char *pin)
-+{
-+	struct pkcs11_uri *uri = pkcs11_uri_init();
-+	if (id_len > 0) {
-+		uri->id_len = id_len;
-+		uri->id = id;
-+	}
-+	uri->module_path = module_path;
-+	uri->token = token;
-+	uri->lib_manuf = lib_manuf;
-+	uri->manuf = manuf;
-+	uri->serial = serial;
-+	uri->object = object;
-+	uri->pin = pin;
-+	return uri;
-+}
-+
-+static void
-+test_parse_valid(void)
-+{
-+	/* path arguments */
-+	check_parse("pkcs11:id=%01",
-+	    compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
-+	check_parse("pkcs11:id=%00%01",
-+	    compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
-+	check_parse("pkcs11:token=SSH%20Keys",
-+	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
-+	check_parse("pkcs11:library-manufacturer=OpenSC",
-+	    compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL, NULL));
-+	check_parse("pkcs11:manufacturer=piv_II",
-+	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL, NULL));
-+	check_parse("pkcs11:serial=IamSerial",
-+	    compose_uri(NULL, 0, NULL, NULL, NULL, "IamSerial", NULL, NULL, NULL));
-+	check_parse("pkcs11:object=SIGN%20Key",
-+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, "SIGN Key", NULL));
-+	/* query arguments */
-+	check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
-+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
-+	check_parse("pkcs11:?pin-value=123456",
-+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, "123456"));
-+
-+	/* combinations */
-+	/* ID SHOULD be percent encoded */
-+	check_parse("pkcs11:token=SSH%20Key;id=0",
-+	    compose_uri("0", 1, "SSH Key", NULL, NULL, NULL, NULL, NULL, NULL));
-+	check_parse(
-+	    "pkcs11:manufacturer=CAC?module-path=/usr/lib64/p11-kit-proxy.so",
-+	    compose_uri(NULL, 0, NULL, NULL, "CAC", NULL,
-+	    "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
-+	check_parse(
-+	    "pkcs11:object=RSA%20Key?module-path=/usr/lib64/pkcs11/opencryptoki.so",
-+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL,
-+	    "/usr/lib64/pkcs11/opencryptoki.so", "RSA Key", NULL));
-+	check_parse("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so&pin-value=123456",
-+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, "123456"));
-+
-+	/* empty path component matches everything */
-+	check_parse("pkcs11:", EMPTY_URI);
-+
-+	/* empty string is a valid to match against (and different from NULL) */
-+	check_parse("pkcs11:token=",
-+	    compose_uri(NULL, 0, "", NULL, NULL, NULL, NULL, NULL, NULL));
-+	/* Percent character needs to be percent-encoded */
-+	check_parse("pkcs11:token=%25",
-+	     compose_uri(NULL, 0, "%", NULL, NULL, NULL, NULL, NULL, NULL));
-+}
-+
-+static void
-+test_parse_invalid(void)
-+{
-+	/* Invalid percent encoding */
-+	check_parse_rv("pkcs11:id=%0", EMPTY_URI, -1);
-+	/* Invalid percent encoding */
-+	check_parse_rv("pkcs11:id=%ZZ", EMPTY_URI, -1);
-+	/* Space MUST be percent encoded -- XXX not enforced yet */
-+	check_parse("pkcs11:token=SSH Keys",
-+	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
-+	/* MUST NOT contain duplicate attributes of the same name */
-+	check_parse_rv("pkcs11:id=%01;id=%02", EMPTY_URI, -1);
-+	/* MUST NOT contain duplicate attributes of the same name */
-+	check_parse_rv("pkcs11:?pin-value=111111&pin-value=123456", EMPTY_URI, -1);
-+	/* Unrecognized attribute in path are ignored with log message */
-+	check_parse("pkcs11:key_name=SSH", EMPTY_URI);
-+	/* Unrecognized attribute in query SHOULD be ignored */
-+	check_parse("pkcs11:?key_name=SSH", EMPTY_URI);
-+}
-+
-+void
-+check_gen(char *expect, struct pkcs11_uri *uri)
-+{
-+	char *buf = NULL, *uri_str;
-+
-+	asprintf(&buf, "Valid PKCS#11 URI generation: %s", expect);
-+	TEST_START(buf);
-+	free(buf);
-+	uri_str = pkcs11_uri_get(uri);
-+	ASSERT_PTR_NE(uri_str, NULL);
-+	ASSERT_STRING_EQ(uri_str, expect);
-+	free(uri_str);
-+	TEST_DONE();
-+}
-+
-+static void
-+test_generate_valid(void)
-+{
-+	/* path arguments */
-+	check_gen("pkcs11:id=%01",
-+	    compose_uri("\x01", 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
-+	check_gen("pkcs11:id=%00%01",
-+	    compose_uri("\x00\x01", 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
-+	check_gen("pkcs11:token=SSH%20Keys", /* space must be percent encoded */
-+	    compose_uri(NULL, 0, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
-+	/* library-manufacturer is not implmented now */
-+	/*check_gen("pkcs11:library-manufacturer=OpenSC",
-+	    compose_uri(NULL, 0, NULL, "OpenSC", NULL, NULL, NULL, NULL, NULL));*/
-+	check_gen("pkcs11:manufacturer=piv_II",
-+	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, NULL, NULL));
-+	check_gen("pkcs11:serial=IamSerial",
-+	    compose_uri(NULL, 0, NULL, NULL, NULL, "IamSerial", NULL, NULL, NULL));
-+	check_gen("pkcs11:object=RSA%20Key",
-+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, NULL, "RSA Key", NULL));
-+	/* query arguments */
-+	check_gen("pkcs11:?module-path=/usr/lib64/p11-kit-proxy.so",
-+	    compose_uri(NULL, 0, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
-+
-+	/* combinations */
-+	check_gen("pkcs11:id=%02;token=SSH%20Keys",
-+	    compose_uri("\x02", 1, "SSH Keys", NULL, NULL, NULL, NULL, NULL, NULL));
-+	check_gen("pkcs11:id=%EE%02?module-path=/usr/lib64/p11-kit-proxy.so",
-+	    compose_uri("\xEE\x02", 2, NULL, NULL, NULL, NULL, "/usr/lib64/p11-kit-proxy.so", NULL, NULL));
-+	check_gen("pkcs11:object=Encryption%20Key;manufacturer=piv_II",
-+	    compose_uri(NULL, 0, NULL, NULL, "piv_II", NULL, NULL, "Encryption Key", NULL));
-+
-+	/* empty path component matches everything */
-+	check_gen("pkcs11:", EMPTY_URI);
-+
-+}
-+
-+void
-+check_encode(char *source, size_t len, char *allow_list, char *expect)
-+{
-+	char *buf = NULL;
-+	struct sshbuf *b;
-+
-+	asprintf(&buf, "percent_encode: expected %s", expect);
-+	TEST_START(buf);
-+	free(buf);
-+
-+	b = percent_encode(source, len, allow_list);
-+	ASSERT_STRING_EQ(sshbuf_ptr(b), expect);
-+	sshbuf_free(b);
-+	TEST_DONE();
-+}
-+
-+static void
-+test_percent_encode_multibyte(void)
-+{
-+	/* SHOULD be encoded as octets according to the UTF-8 character encoding */
-+
-+	/* multi-byte characters are "for free" */
-+	check_encode("$", 1, "", "%24");
-+	check_encode("¢", 2, "", "%C2%A2");
-+	check_encode("€", 3, "", "%E2%82%AC");
-+	check_encode("𐍈", 4, "", "%F0%90%8D%88");
-+
-+	/* CK_UTF8CHAR is unsigned char (1 byte) */
-+	/* labels SHOULD be normalized to NFC [UAX15] */
-+
-+}
-+
-+static void
-+test_percent_encode(void)
-+{
-+	/* Without allow list encodes everything (for CKA_ID) */
-+	check_encode("A*", 2, "", "%41%2A");
-+	check_encode("\x00", 1, "", "%00");
-+	check_encode("\x7F", 1, "", "%7F");
-+	check_encode("\x80", 1, "", "%80");
-+	check_encode("\xff", 1, "", "%FF");
-+
-+	/* Default allow list encodes anything but safe letters */
-+	check_encode("test" "\x00" "0alpha", 11, PKCS11_URI_WHITELIST,
-+	    "test%000alpha");
-+	check_encode(" ", 1, PKCS11_URI_WHITELIST,
-+	    "%20"); /* Space MUST be percent encoded */
-+	check_encode("/", 1, PKCS11_URI_WHITELIST,
-+	    "%2F"); /* '/' delimiter MUST be percent encoded (in the path) */
-+	check_encode("?", 1, PKCS11_URI_WHITELIST,
-+	    "%3F"); /* delimiter '?' MUST be percent encoded (in the path) */
-+	check_encode("#", 1, PKCS11_URI_WHITELIST,
-+	    "%23"); /* '#' MUST be always percent encoded */
-+	check_encode("key=value;separator?query&amp;#anch", 35, PKCS11_URI_WHITELIST,
-+	    "key%3Dvalue%3Bseparator%3Fquery%26amp%3B%23anch");
-+
-+	/* Components in query can have '/' unencoded (useful for paths) */
-+	check_encode("/path/to.file", 13, PKCS11_URI_WHITELIST "/",
-+	    "/path/to.file");
-+}
-+
-+void
-+check_decode(char *source, char *expect, int expect_len)
-+{
-+	char *buf = NULL, *out = NULL;
-+	int rv;
-+
-+	asprintf(&buf, "percent_decode: %s", source);
-+	TEST_START(buf);
-+	free(buf);
-+
-+	rv = percent_decode(source, &out);
-+	ASSERT_INT_EQ(rv, expect_len);
-+	if (rv >= 0)
-+		ASSERT_MEM_EQ(out, expect, expect_len);
-+	free(out);
-+	TEST_DONE();
-+}
-+
-+static void
-+test_percent_decode(void)
-+{
-+	/* simple valid cases */
-+	check_decode("%00", "\x00", 1);
-+	check_decode("%FF", "\xFF", 1);
-+
-+	/* normal strings shold be kept intact */
-+	check_decode("strings are left", "strings are left", 16);
-+	check_decode("10%25 of trees", "10% of trees", 12);
-+
-+	/* make sure no more than 2 bytes are parsed */
-+	check_decode("%222", "\x22" "2", 2);
-+
-+	/* invalid expects failure */
-+	check_decode("%0", "", -1);
-+	check_decode("%Z", "", -1);
-+	check_decode("%FG", "", -1);
-+}
-+
-+void
-+tests(void)
-+{
-+	test_percent_encode();
-+	test_percent_encode_multibyte();
-+	test_percent_decode();
-+	test_parse_valid();
-+	test_parse_invalid();
-+	test_generate_valid();
-+}
-+
-+void
-+benchmarks(void)
-+{
-+	printf("no benchmarks\n");
-+}
-+
-diff --git a/ssh-add.c b/ssh-add.c
-index 1e9eddf90..e476fa662 100644
---- a/ssh-add.c
-+++ b/ssh-add.c
-@@ -70,6 +70,7 @@
- #include "ssh-sk.h"
- #include "sk-api.h"
- #include "hostfile.h"
-+#include "ssh-pkcs11-uri.h"
- 
- #define CERT_EXPIRY_GRACE	(5*60)
- 
-@@ -272,6 +273,38 @@ check_cert_lifetime(const struct sshkey *cert, int cert_lifetime)
- 	return MINIMUM(cert_lifetime, (int)n);
- }
- 
-+#ifdef ENABLE_PKCS11
-+static int
-+update_card(int agent_fd, int add, const char *id, int qflag,
-+    int key_only, int cert_only,
-+    struct dest_constraint **dest_constraints, size_t ndest_constraints,
-+    struct sshkey **certs, size_t ncerts, char *pin);
-+
-+int
-+update_pkcs11_uri(int agent_fd, int adding, const char *pkcs11_uri, int qflag,
-+    struct dest_constraint **dest_constraints, size_t ndest_constraints)
-+{
-+	char *pin = NULL;
-+	struct pkcs11_uri *uri;
-+
-+	/* dry-run parse to make sure the URI is valid and to report errors */
-+	uri = pkcs11_uri_init();
-+	if (pkcs11_uri_parse((char *) pkcs11_uri, uri) != 0)
-+		fatal("Failed to parse PKCS#11 URI");
-+	if (uri->pin != NULL) {
-+		pin = strdup(uri->pin);
-+		if (pin == NULL) {
-+			fatal("Failed to dupplicate string");
-+		}
-+		/* pin is freed in the update_card() */
-+	}
-+	pkcs11_uri_cleanup(uri);
-+
-+	return update_card(agent_fd, adding, pkcs11_uri, qflag, 1, 0,
-+	           dest_constraints, ndest_constraints, NULL, 0, pin);
-+}
-+#endif
-+
- static int
- add_file(int agent_fd, const char *filename, int key_only, int cert_only,
-     int qflag, int Nflag, const char *skprovider,
-@@ -462,15 +495,14 @@ static int
- update_card(int agent_fd, int add, const char *id, int qflag,
-     int key_only, int cert_only,
-     struct dest_constraint **dest_constraints, size_t ndest_constraints,
--    struct sshkey **certs, size_t ncerts)
-+    struct sshkey **certs, size_t ncerts, char *pin)
- {
--	char *pin = NULL;
- 	int r, ret = -1;
- 
- 	if (key_only)
- 		ncerts = 0;
- 
--	if (add) {
-+	if (add && pin == NULL) {
- 		if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
- 		    RP_ALLOW_STDIN)) == NULL)
- 			return -1;
-@@ -652,6 +684,14 @@ do_file(int agent_fd, int deleting, int key_only, int cert_only,
-     char *file, int qflag, int Nflag, const char *skprovider,
-     struct dest_constraint **dest_constraints, size_t ndest_constraints)
- {
-+#ifdef ENABLE_PKCS11
-+	if (strlen(file) >= strlen(PKCS11_URI_SCHEME) &&
-+	    strncmp(file, PKCS11_URI_SCHEME,
-+	    strlen(PKCS11_URI_SCHEME)) == 0) {
-+		return update_pkcs11_uri(agent_fd, !deleting, file, qflag,
-+                   dest_constraints, ndest_constraints);
-+	}
-+#endif
- 	if (deleting) {
- 		if (delete_file(agent_fd, file, key_only,
- 		    cert_only, qflag) == -1)
-@@ -1003,7 +1043,7 @@ main(int argc, char **argv)
- 		if (update_card(agent_fd, !deleting, pkcs11provider,
- 		    qflag, key_only, cert_only,
- 		    dest_constraints, ndest_constraints,
--		    certs, ncerts) == -1)
-+		    certs, ncerts, NULL) == -1)
- 			ret = 1;
- 		for (n = 0; n < ncerts; n++)
- 			sshkey_free(certs[n]);
-diff --git a/ssh-agent.c b/ssh-agent.c
-index 7773a4b07..3451d7a14 100644
---- a/ssh-agent.c
-+++ b/ssh-agent.c
-@@ -1546,10 +1546,75 @@ add_p11_identity(struct sshkey *key, char *comment, const char *provider,
- 	idtab->nentries++;
- }
- 
-+static char *
-+sanitize_pkcs11_provider(const char *provider)
-+{
-+	struct pkcs11_uri *uri = NULL;
-+	char *sane_uri, *module_path = NULL; /* default path */
-+	char canonical_provider[PATH_MAX];
-+
-+	if (provider == NULL)
-+		return NULL;
-+
-+	memset(canonical_provider, 0, sizeof(canonical_provider));
-+
-+	if (strlen(provider) >= strlen(PKCS11_URI_SCHEME) &&
-+	    strncmp(provider, PKCS11_URI_SCHEME,
-+	    strlen(PKCS11_URI_SCHEME)) == 0) {
-+		/* PKCS#11 URI */
-+		uri = pkcs11_uri_init();
-+		if (uri == NULL) {
-+			error("Failed to init PKCS#11 URI");
-+			return NULL;
-+		}
-+
-+		if (pkcs11_uri_parse(provider, uri) != 0) {
-+			error("Failed to parse PKCS#11 URI");
-+			pkcs11_uri_cleanup(uri);
-+			return NULL;
-+		}
-+		/* validate also provider from URI */
-+		if (uri->module_path)
-+			module_path = strdup(uri->module_path);
-+	} else
-+		module_path = strdup(provider); /* simple path */
-+
-+	if (module_path != NULL) { /* do not validate default NULL path in URI */
-+		if (realpath(module_path, canonical_provider) == NULL) {
-+			verbose("failed PKCS#11 provider \"%.100s\": realpath: %s",
-+			    module_path, strerror(errno));
-+			free(module_path);
-+			pkcs11_uri_cleanup(uri);
-+			return NULL;
-+		}
-+		free(module_path);
-+		if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) {
-+			verbose("refusing PKCS#11 provider \"%.100s\": "
-+			    "not allowed", canonical_provider);
-+			pkcs11_uri_cleanup(uri);
-+			return NULL;
-+		}
-+
-+		/* copy verified and sanitized provider path back to the uri */
-+		if (uri) {
-+			free(uri->module_path);
-+			uri->module_path = xstrdup(canonical_provider);
-+		}
-+	}
-+
-+	if (uri) {
-+		sane_uri = pkcs11_uri_get(uri);
-+		pkcs11_uri_cleanup(uri);
-+		return sane_uri;
-+	} else {
-+		return xstrdup(canonical_provider); /* simple path */
-+	}
-+}
-+
- static void
- process_add_smartcard_key(SocketEntry *e)
- {
--	char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
-+	char *provider = NULL, *pin = NULL, *sane_uri = NULL;
- 	char **comments = NULL;
- 	int r, i, count = 0, success = 0, confirm = 0;
- 	u_int seconds = 0;
-@@ -1578,25 +1643,18 @@ process_add_smartcard_key(SocketEntry *e)
- 		    "providers is disabled", provider);
- 		goto send;
- 	}
--	if (realpath(provider, canonical_provider) == NULL) {
--		verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
--		    provider, strerror(errno));
--		goto send;
--	}
--	if (match_pattern_list(canonical_provider, allowed_providers, 0) != 1) {
--		verbose("refusing PKCS#11 add of \"%.100s\": "
--		    "provider not allowed", canonical_provider);
-+	sane_uri = sanitize_pkcs11_provider(provider);
-+	if (sane_uri == NULL)
- 		goto send;
--	}
--	debug_f("add %.100s", canonical_provider);
- 	if (lifetime && !death)
- 		death = monotime() + lifetime;
- 
--	count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments);
-+	debug_f("add %.100s", sane_uri);
-+	count = pkcs11_add_provider(sane_uri, pin, &keys, &comments);
- 	for (i = 0; i < count; i++) {
- 		if (comments[i] == NULL || comments[i][0] == '\0') {
- 			free(comments[i]);
--			comments[i] = xstrdup(canonical_provider);
-+			comments[i] = xstrdup(sane_uri);
- 		}
- 		for (j = 0; j < ncerts; j++) {
- 			if (!sshkey_is_cert(certs[j]))
-@@ -1606,13 +1664,13 @@ process_add_smartcard_key(SocketEntry *e)
- 			if (pkcs11_make_cert(keys[i], certs[j], &k) != 0)
- 				continue;
- 			add_p11_identity(k, xstrdup(comments[i]),
--			    canonical_provider, death, confirm,
-+			    sane_uri, death, confirm,
- 			    dest_constraints, ndest_constraints);
- 			success = 1;
- 		}
- 		if (!cert_only && lookup_identity(keys[i]) == NULL) {
- 			add_p11_identity(keys[i], comments[i],
--			    canonical_provider, death, confirm,
-+			    sane_uri, death, confirm,
- 			    dest_constraints, ndest_constraints);
- 			keys[i] = NULL;		/* transferred */
- 			comments[i] = NULL;	/* transferred */
-@@ -1625,6 +1683,7 @@ process_add_smartcard_key(SocketEntry *e)
- send:
- 	free(pin);
- 	free(provider);
-+	free(sane_uri);
- 	free(keys);
- 	free(comments);
- 	free_dest_constraints(dest_constraints, ndest_constraints);
-@@ -1637,8 +1696,8 @@ send:
- static void
- process_remove_smartcard_key(SocketEntry *e)
- {
--	char *provider = NULL, *pin = NULL, canonical_provider[PATH_MAX];
--	int r, success = 0;
-+	char *provider = NULL, *pin = NULL, *sane_uri = NULL;
-+	int r, success = 0, removed_count = 0;
- 	Identity *id, *nxt;
- 
- 	debug2_f("entering");
-@@ -1649,30 +1708,38 @@ process_remove_smartcard_key(SocketEntry *e)
- 	}
- 	free(pin);
- 
--	if (realpath(provider, canonical_provider) == NULL) {
--		verbose("failed PKCS#11 add of \"%.100s\": realpath: %s",
--		    provider, strerror(errno));
-+	sane_uri = sanitize_pkcs11_provider(provider);
-+	if (sane_uri == NULL)
- 		goto send;
--	}
- 
--	debug_f("remove %.100s", canonical_provider);
-+	debug_f("remove %.100s", sane_uri);
- 	for (id = TAILQ_FIRST(&idtab->idlist); id; id = nxt) {
- 		nxt = TAILQ_NEXT(id, next);
- 		/* Skip file--based keys */
- 		if (id->provider == NULL)
- 			continue;
--		if (!strcmp(canonical_provider, id->provider)) {
-+		if (!strcmp(sane_uri, id->provider)) {
- 			TAILQ_REMOVE(&idtab->idlist, id, next);
- 			free_identity(id);
- 			idtab->nentries--;
-+			removed_count++;
- 		}
- 	}
--	if (pkcs11_del_provider(canonical_provider) == 0)
-+	/* Only succeed if we actually removed keys and unloaded provider */
-+	if (removed_count > 0 && pkcs11_del_provider(sane_uri) == 0) {
- 		success = 1;
--	else
-+		debug_f("removed %d key(s) from provider %s",
-+		    removed_count, sane_uri);
-+	} else if (removed_count == 0) {
-+		error_f("no matching keys found for provider %s", sane_uri);
-+		/* Still try to clean up provider if it exists */
-+		pkcs11_del_provider(sane_uri);
-+	} else {
- 		error_f("pkcs11_del_provider failed");
-+	}
- send:
- 	free(provider);
-+	free(sane_uri);
- 	send_status(e, success);
- }
- #endif /* ENABLE_PKCS11 */
-diff --git a/ssh-keygen.c b/ssh-keygen.c
-index 12430e6a4..266462f07 100644
---- a/ssh-keygen.c
-+++ b/ssh-keygen.c
-@@ -831,8 +831,11 @@ do_download(struct passwd *pw)
- 			free(fp);
- 		} else {
- 			(void) sshkey_write(keys[i], stdout); /* XXX check */
--			fprintf(stdout, "%s%s\n",
--			    *(comments[i]) == '\0' ? "" : " ", comments[i]);
-+			if (*(comments[i]) != '\0') {
-+				fprintf(stdout, " %s", comments[i]);
-+			}
-+			(void) pkcs11_uri_write(keys[i], stdout);
-+			fprintf(stdout, "\n");
- 		}
- 		free(comments[i]);
- 		sshkey_free(keys[i]);
-diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c
-index 30a4cf5dc..3fd058a0c 100644
---- a/ssh-pkcs11-client.c
-+++ b/ssh-pkcs11-client.c
-@@ -18,6 +18,7 @@
- 
- #include "includes.h"
- 
-+#ifdef ENABLE_PKCS11
- #include <sys/types.h>
- #include <sys/time.h>
- #include <sys/socket.h>
-@@ -38,6 +39,7 @@
- #include "authfd.h"
- #include "atomicio.h"
- #include "ssh-pkcs11.h"
-+#include "ssh-pkcs11-uri.h"
- #include "ssherr.h"
- 
- /* borrows code from sftp-server and ssh-agent */
-@@ -379,6 +381,19 @@ pkcs11_start_helper(const char *path)
- 	return helper;
- }
- 
-+int
-+pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin, struct sshkey ***keyp, char ***labelsp)
-+{
-+	int nkeys = 0;
-+	char *provider_uri = pkcs11_uri_get(uri);
-+
-+	debug_f("called, provider_uri = %s", provider_uri);
-+
-+	nkeys = pkcs11_add_provider(provider_uri, pin, keyp, labelsp);
-+
-+	return nkeys;
-+}
-+
- int
- pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
-     char ***labelsp)
-@@ -390,6 +405,8 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
- 	struct sshbuf *msg;
- 	struct helper *helper;
- 
-+	debug_f("called, name = %s", name);
-+
- 	if ((helper = helper_by_provider(name)) == NULL &&
- 	    (helper = pkcs11_start_helper(name)) == NULL)
- 		return -1;
-@@ -414,6 +431,7 @@ pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp,
- 		*keysp = xcalloc(nkeys, sizeof(struct sshkey *));
- 		if (labelsp)
- 			*labelsp = xcalloc(nkeys, sizeof(char *));
-+		debug_f("nkeys = %u", nkeys);
- 		for (i = 0; i < nkeys; i++) {
- 			/* XXX clean up properly instead of fatal() */
- 			if ((r = sshkey_froms(msg, &k)) != 0 ||
-@@ -497,3 +515,4 @@ pkcs11_key_free(struct sshkey *key)
- 	if (helper->nkeyblobs == 0)
- 		helper_terminate(helper);
- }
-+#endif
-diff --git a/ssh-pkcs11-uri.c b/ssh-pkcs11-uri.c
-new file mode 100644
-index 000000000..d5b074592
---- /dev/null
-+++ b/ssh-pkcs11-uri.c
-@@ -0,0 +1,438 @@
-+/*
-+ * Copyright (c) 2017 Red Hat
-+ *
-+ * Authors: Jakub Jelen <jjelen@redhat.com>
-+ *
-+ * 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 "includes.h"
-+
-+#ifdef ENABLE_PKCS11
-+
-+#include <stdio.h>
-+#include <string.h>
-+#include <stdlib.h>
-+
-+#include "sshkey.h"
-+#include "sshbuf.h"
-+#include "log.h"
-+
-+#define CRYPTOKI_COMPAT
-+#include "pkcs11.h"
-+
-+#include "ssh-pkcs11-uri.h"
-+
-+#define PKCS11_URI_PATH_SEPARATOR ";"
-+#define PKCS11_URI_QUERY_SEPARATOR "&"
-+#define PKCS11_URI_VALUE_SEPARATOR "="
-+#define PKCS11_URI_ID "id"
-+#define PKCS11_URI_TOKEN "token"
-+#define PKCS11_URI_OBJECT "object"
-+#define PKCS11_URI_LIB_MANUF "library-manufacturer"
-+#define PKCS11_URI_MANUF "manufacturer"
-+#define PKCS11_URI_SERIAL "serial"
-+#define PKCS11_URI_MODULE_PATH "module-path"
-+#define PKCS11_URI_PIN_VALUE "pin-value"
-+
-+/* Keyword tokens. */
-+typedef enum {
-+	pId, pToken, pObject, pLibraryManufacturer, pManufacturer, pSerial,
-+	pModulePath, pPinValue, pBadOption
-+} pkcs11uriOpCodes;
-+
-+/* Textual representation of the tokens. */
-+static struct {
-+	const char *name;
-+	pkcs11uriOpCodes opcode;
-+} keywords[] = {
-+	{ PKCS11_URI_ID, pId },
-+	{ PKCS11_URI_TOKEN, pToken },
-+	{ PKCS11_URI_OBJECT, pObject },
-+	{ PKCS11_URI_LIB_MANUF, pLibraryManufacturer },
-+	{ PKCS11_URI_MANUF, pManufacturer },
-+	{ PKCS11_URI_SERIAL, pSerial },
-+	{ PKCS11_URI_MODULE_PATH, pModulePath },
-+	{ PKCS11_URI_PIN_VALUE, pPinValue },
-+	{ NULL, pBadOption }
-+};
-+
-+static pkcs11uriOpCodes
-+parse_token(const char *cp)
-+{
-+	u_int i;
-+
-+	for (i = 0; keywords[i].name; i++)
-+		if (strncasecmp(cp, keywords[i].name,
-+		    strlen(keywords[i].name)) == 0)
-+			return keywords[i].opcode;
-+
-+	return pBadOption;
-+}
-+
-+int
-+percent_decode(char *data, char **outp)
-+{
-+	char tmp[3];
-+	char *out, *tmp_end;
-+	char *p = data;
-+	long value;
-+	size_t outlen = 0;
-+
-+	out = malloc(strlen(data)+1); /* upper bound */
-+	if (out == NULL)
-+		return -1;
-+	while (*p != '\0') {
-+		switch (*p) {
-+		case '%':
-+			p++;
-+			if (*p == '\0')
-+				goto fail;
-+			tmp[0] = *p++;
-+			if (*p == '\0')
-+				goto fail;
-+			tmp[1] = *p++;
-+			tmp[2] = '\0';
-+			tmp_end = NULL;
-+			value = strtol(tmp, &tmp_end, 16);
-+			if (tmp_end != tmp+2)
-+				goto fail;
-+			else
-+				out[outlen++] = (char) value;
-+			break;
-+		default:
-+			out[outlen++] = *p++;
-+			break;
-+		}
-+	}
-+
-+	/* zero terminate */
-+	out[outlen] = '\0';
-+	*outp = out;
-+	return outlen;
-+fail:
-+	free(out);
-+	return -1;
-+}
-+
-+struct sshbuf *
-+percent_encode(const char *data, size_t length, const char *allow_list)
-+{
-+	struct sshbuf *b = NULL;
-+	char tmp[4], *cp;
-+	size_t i;
-+
-+	if ((b = sshbuf_new()) == NULL)
-+		return NULL;
-+	for (i = 0; i < length; i++) {
-+		cp = strchr(allow_list, data[i]);
-+		/* if c is specified as '\0' pointer to terminator is returned !! */
-+		if (cp != NULL && *cp != '\0') {
-+			if (sshbuf_put(b, &data[i], 1) != 0)
-+				goto err;
-+		} else
-+			if (snprintf(tmp, 4, "%%%02X", (unsigned char) data[i]) < 3
-+			    || sshbuf_put(b, tmp, 3) != 0)
-+				goto err;
-+	}
-+	if (sshbuf_put(b, "\0", 1) == 0)
-+		return b;
-+err:
-+	sshbuf_free(b);
-+	return NULL;
-+}
-+
-+char *
-+pkcs11_uri_append(char *part, const char *separator, const char *key,
-+    struct sshbuf *value)
-+{
-+	char *new_part;
-+	size_t size = 0;
-+
-+	if (value == NULL)
-+		return NULL;
-+
-+	size = asprintf(&new_part,
-+	    "%s%s%s"  PKCS11_URI_VALUE_SEPARATOR "%s",
-+	    (part != NULL ? part : ""),
-+	    (part != NULL ? separator : ""),
-+	    key, sshbuf_ptr(value));
-+	sshbuf_free(value);
-+	free(part);
-+
-+	if (size <= 0)
-+		return NULL;
-+	return new_part;
-+}
-+
-+char *
-+pkcs11_uri_get(struct pkcs11_uri *uri)
-+{
-+	size_t size = 0;
-+	char *p = NULL, *path = NULL, *query = NULL;
-+
-+	/* compose a percent-encoded ID */
-+	if (uri->id_len > 0) {
-+		struct sshbuf *key_id = percent_encode(uri->id, uri->id_len, "");
-+		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+		    PKCS11_URI_ID, key_id);
-+		if (path == NULL)
-+			goto err;
-+	}
-+
-+	/* Write object label */
-+	if (uri->object) {
-+		struct sshbuf *label = percent_encode(uri->object, strlen(uri->object),
-+		    PKCS11_URI_WHITELIST);
-+		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+		    PKCS11_URI_OBJECT, label);
-+		if (path == NULL)
-+			goto err;
-+	}
-+
-+	/* Write token label */
-+	if (uri->token) {
-+		struct sshbuf *label = percent_encode(uri->token, strlen(uri->token),
-+		    PKCS11_URI_WHITELIST);
-+		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+		    PKCS11_URI_TOKEN, label);
-+		if (path == NULL)
-+			goto err;
-+	}
-+
-+	/* Write manufacturer */
-+	if (uri->manuf) {
-+		struct sshbuf *manuf = percent_encode(uri->manuf,
-+		    strlen(uri->manuf), PKCS11_URI_WHITELIST);
-+		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+		    PKCS11_URI_MANUF, manuf);
-+		if (path == NULL)
-+			goto err;
-+	}
-+
-+	/* Write serial */
-+	if (uri->serial) {
-+		struct sshbuf *serial = percent_encode(uri->serial,
-+		    strlen(uri->serial), PKCS11_URI_WHITELIST);
-+		path = pkcs11_uri_append(path, PKCS11_URI_PATH_SEPARATOR,
-+		    PKCS11_URI_SERIAL, serial);
-+		if (path == NULL)
-+			goto err;
-+	}
-+
-+	/* Write module_path */
-+	if (uri->module_path) {
-+		struct sshbuf *module = percent_encode(uri->module_path,
-+		    strlen(uri->module_path), PKCS11_URI_WHITELIST "/");
-+		query = pkcs11_uri_append(query, PKCS11_URI_QUERY_SEPARATOR,
-+		    PKCS11_URI_MODULE_PATH, module);
-+		if (query == NULL)
-+			goto err;
-+	}
-+
-+	size = asprintf(&p, PKCS11_URI_SCHEME "%s%s%s",
-+	    path != NULL ? path : "",
-+	    query != NULL ? "?" : "",
-+	    query != NULL ? query : "");
-+err:
-+	free(query);
-+	free(path);
-+	if (size <= 0)
-+		return NULL;
-+	return p;
-+}
-+
-+struct pkcs11_uri *
-+pkcs11_uri_init()
-+{
-+	struct pkcs11_uri *d = calloc(1, sizeof(struct pkcs11_uri));
-+	return d;
-+}
-+
-+void
-+pkcs11_uri_cleanup(struct pkcs11_uri *pkcs11)
-+{
-+	if (pkcs11 == NULL) {
-+		return;
-+	}
-+
-+	free(pkcs11->id);
-+	free(pkcs11->module_path);
-+	free(pkcs11->token);
-+	free(pkcs11->object);
-+	free(pkcs11->lib_manuf);
-+	free(pkcs11->manuf);
-+	free(pkcs11->serial);
-+	if (pkcs11->pin)
-+		freezero(pkcs11->pin, strlen(pkcs11->pin));
-+	free(pkcs11);
-+}
-+
-+int
-+pkcs11_uri_parse(const char *uri, struct pkcs11_uri *pkcs11)
-+{
-+	char *saveptr1, *saveptr2, *str1, *str2, *tok;
-+	int rv = 0, len;
-+	char *p = NULL;
-+
-+	size_t scheme_len = strlen(PKCS11_URI_SCHEME);
-+	if (strlen(uri) < scheme_len || /* empty URI matches everything */
-+	    strncmp(uri, PKCS11_URI_SCHEME, scheme_len) != 0) {
-+		error_f("The '%s' does not look like PKCS#11 URI", uri);
-+		return -1;
-+	}
-+
-+	if (pkcs11 == NULL) {
-+		error_f("Bad arguments. The pkcs11 can't be null");
-+		return -1;
-+	}
-+
-+	/* skip URI schema name */
-+	p = strdup(uri);
-+	str1 = p;
-+
-+	/* everything before ? */
-+	tok = strtok_r(str1, "?", &saveptr1);
-+	if (tok == NULL) {
-+		error_f("pk11-path expected, got EOF");
-+		rv = -1;
-+		goto out;
-+	}
-+
-+	/* skip URI schema name:
-+	 * the scheme ensures that there is at least something before "?"
-+	 * allowing empty pk11-path. Resulting token at worst pointing to
-+	 * \0 byte */
-+	tok = tok + scheme_len;
-+
-+	/* parse pk11-path */
-+	for (str2 = tok; ; str2 = NULL) {
-+		char **charptr, *arg = NULL;
-+		pkcs11uriOpCodes opcode;
-+		tok = strtok_r(str2, PKCS11_URI_PATH_SEPARATOR, &saveptr2);
-+		if (tok == NULL)
-+			break;
-+		opcode = parse_token(tok);
-+		if (opcode != pBadOption)
-+			arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
-+
-+		switch (opcode) {
-+		case pId:
-+			/* CKA_ID */
-+			if (pkcs11->id != NULL) {
-+				verbose_f("The id already set in the PKCS#11 URI");
-+				rv = -1;
-+				goto out;
-+			}
-+			len = percent_decode(arg, &pkcs11->id);
-+			if (len <= 0) {
-+				verbose_f("Failed to percent-decode CKA_ID: %s", arg);
-+				rv = -1;
-+				goto out;
-+			} else
-+				pkcs11->id_len = len;
-+			debug3_f("Setting CKA_ID = %s from PKCS#11 URI", arg);
-+			break;
-+		case pToken:
-+			/* CK_TOKEN_INFO -> label */
-+			charptr = &pkcs11->token;
-+ parse_string:
-+			if (*charptr != NULL) {
-+				verbose_f("The %s already set in the PKCS#11 URI",
-+				    keywords[opcode].name);
-+				rv = -1;
-+				goto out;
-+			}
-+			percent_decode(arg, charptr);
-+			debug3_f("Setting %s = %s from PKCS#11 URI",
-+			    keywords[opcode].name, *charptr);
-+			break;
-+
-+		case pObject:
-+			/* CK_TOKEN_INFO -> manufacturerID */
-+			charptr = &pkcs11->object;
-+			goto parse_string;
-+
-+		case pManufacturer:
-+			/* CK_TOKEN_INFO -> manufacturerID */
-+			charptr = &pkcs11->manuf;
-+			goto parse_string;
-+
-+		case pSerial:
-+			/* CK_TOKEN_INFO -> serialNumber */
-+			charptr = &pkcs11->serial;
-+			goto parse_string;
-+
-+		case pLibraryManufacturer:
-+			/* CK_INFO -> manufacturerID */
-+			charptr = &pkcs11->lib_manuf;
-+			goto parse_string;
-+
-+		default:
-+			/* Unrecognized attribute in the URI path SHOULD be error */
-+			verbose_f("Unknown part of path in PKCS#11 URI: %s", tok);
-+		}
-+	}
-+
-+	tok = strtok_r(NULL, "?", &saveptr1);
-+	if (tok == NULL) {
-+		goto out;
-+	}
-+	/* parse pk11-query (optional) */
-+	for (str2 = tok; ; str2 = NULL) {
-+		char *arg;
-+		pkcs11uriOpCodes opcode;
-+		tok = strtok_r(str2, PKCS11_URI_QUERY_SEPARATOR, &saveptr2);
-+		if (tok == NULL)
-+			break;
-+		opcode = parse_token(tok);
-+		if (opcode != pBadOption)
-+			arg = tok + strlen(keywords[opcode].name) + 1; /* separator "=" */
-+
-+		switch (opcode) {
-+		case pModulePath:
-+			/* module-path is PKCS11Provider */
-+			if (pkcs11->module_path != NULL) {
-+				verbose_f("Multiple module-path attributes are"
-+				    "not supported the PKCS#11 URI");
-+				rv = -1;
-+				goto out;
-+			}
-+			percent_decode(arg, &pkcs11->module_path);
-+			debug3_f("Setting PKCS11Provider = %s from PKCS#11 URI",
-+			    pkcs11->module_path);
-+			break;
-+
-+		case pPinValue:
-+			/* pin-value */
-+			if (pkcs11->pin != NULL) {
-+				verbose_f("Multiple pin-value attributes are"
-+				    "not supported the PKCS#11 URI");
-+				rv = -1;
-+				goto out;
-+			}
-+			percent_decode(arg, &pkcs11->pin);
-+			debug3_f("Setting PIN from PKCS#11 URI");
-+			break;
-+
-+		default:
-+			/* Unrecognized attribute in the URI query SHOULD be ignored */
-+			verbose_f("Unknown part of query in PKCS#11 URI: %s", tok);
-+		}
-+	}
-+out:
-+	free(p);
-+	return rv;
-+}
-+
-+#endif /* ENABLE_PKCS11 */
-diff --git a/ssh-pkcs11-uri.h b/ssh-pkcs11-uri.h
-new file mode 100644
-index 000000000..bc758e760
---- /dev/null
-+++ b/ssh-pkcs11-uri.h
-@@ -0,0 +1,46 @@
-+/*
-+ * Copyright (c) 2017 Red Hat
-+ *
-+ * Authors: Jakub Jelen <jjelen@redhat.com>
-+ *
-+ * 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.
-+ */
-+
-+#ifndef _SSH_PKCS11_URI_H
-+#define _SSH_PKCS11_URI_H
-+
-+#define PKCS11_URI_SCHEME "pkcs11:"
-+#define PKCS11_URI_WHITELIST	"abcdefghijklmnopqrstuvwxyz" \
-+				"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
-+				"0123456789_-.()"
-+
-+struct pkcs11_uri {
-+	/* path */
-+	char *id;
-+	size_t id_len;
-+	char *token;
-+	char *object;
-+	char *lib_manuf;
-+	char *manuf;
-+	char *serial;
-+	/* query */
-+	char *module_path;
-+	char *pin; /* Only parsed, but not printed */
-+};
-+
-+struct	 pkcs11_uri *pkcs11_uri_init();
-+void	 pkcs11_uri_cleanup(struct pkcs11_uri *);
-+int	 pkcs11_uri_parse(const char *, struct pkcs11_uri *);
-+struct	 pkcs11_uri *pkcs11_uri_init();
-+char	*pkcs11_uri_get(struct pkcs11_uri *uri);
-+#endif /* _SSH_PKCS11_URI_H */
-diff --git a/ssh-pkcs11.c b/ssh-pkcs11.c
-index 7a7d3b8ea..6f23fc38b 100644
---- a/ssh-pkcs11.c
-+++ b/ssh-pkcs11.c
-@@ -36,6 +36,7 @@
- #include <openssl/ecdsa.h>
- #include <openssl/x509.h>
- #include <openssl/err.h>
-+#include <openssl/evp.h>
- #endif
- 
- #define CRYPTOKI_COMPAT
-@@ -48,6 +49,7 @@
- #include "misc.h"
- #include "sshbuf.h"
- #include "ssh-pkcs11.h"
-+#include "ssh-pkcs11-uri.h"
- #include "digest.h"
- #include "xmalloc.h"
- #include "crypto_api.h"
-@@ -58,8 +60,8 @@ struct pkcs11_slotinfo {
- 	int			logged_in;
- };
- 
--struct pkcs11_provider {
--	char			*name;
-+struct pkcs11_module {
-+	char			*module_path;
- 	void			*handle;
- 	CK_FUNCTION_LIST	*function_list;
- 	CK_INFO			info;
-@@ -68,6 +70,13 @@ struct pkcs11_provider {
- 	struct pkcs11_slotinfo	*slotinfo;
- 	int			valid;
- 	int			refcount;
-+};
-+
-+struct pkcs11_provider {
-+	char			*name;
-+	struct pkcs11_module	*module; /* can be shared between various providers */
-+	int			refcount;
-+	int			valid;
- 	TAILQ_ENTRY(pkcs11_provider) next;
- };
- 
-@@ -79,6 +88,7 @@ struct pkcs11_key {
- 	CK_ULONG		slotidx;
- 	char			*keyid;
- 	int			keyid_len;
-+	char			*label;
- 	TAILQ_ENTRY(pkcs11_key)	next;
- };
- 
-@@ -105,26 +115,61 @@ ossl_error(const char *msg)
-  * this is called when a provider gets unregistered.
-  */
- static void
--pkcs11_provider_finalize(struct pkcs11_provider *p)
-+pkcs11_module_finalize(struct pkcs11_module *m)
- {
- 	CK_RV rv;
- 	CK_ULONG i;
- 
--	debug_f("provider \"%s\" refcount %d valid %d",
--	    p->name, p->refcount, p->valid);
--	if (!p->valid)
-+	debug_f("%p refcount %d valid %d", m, m->refcount, m->valid);
-+	if (!m->valid)
- 		return;
--	for (i = 0; i < p->nslots; i++) {
--		if (p->slotinfo[i].session &&
--		    (rv = p->function_list->C_CloseSession(
--		    p->slotinfo[i].session)) != CKR_OK)
-+	for (i = 0; i < m->nslots; i++) {
-+		if (m->slotinfo[i].session &&
-+		    (rv = m->function_list->C_CloseSession(
-+		    m->slotinfo[i].session)) != CKR_OK)
- 			error("C_CloseSession failed: %lu", rv);
- 	}
--	if ((rv = p->function_list->C_Finalize(NULL)) != CKR_OK)
-+	if ((rv = m->function_list->C_Finalize(NULL)) != CKR_OK)
- 		error("C_Finalize failed: %lu", rv);
-+	m->valid = 0;
-+	m->function_list = NULL;
-+	dlclose(m->handle);
-+}
-+
-+/*
-+ * remove a reference to the pkcs11 module.
-+ * called when a provider is unregistered.
-+ */
-+static void
-+pkcs11_module_unref(struct pkcs11_module *m)
-+{
-+	debug_f("%p refcount %d", m, m->refcount);
-+	if (--m->refcount <= 0) {
-+		pkcs11_module_finalize(m);
-+		if (m->valid)
-+			error_f("%p still valid", m);
-+		free(m->slotlist);
-+		free(m->slotinfo);
-+		free(m->module_path);
-+		free(m);
-+	}
-+}
-+
-+/*
-+ * finalize a provider shared library, it's no longer usable.
-+ * however, there might still be keys referencing this provider,
-+ * so the actual freeing of memory is handled by pkcs11_provider_unref().
-+ * this is called when a provider gets unregistered.
-+ */
-+static void
-+pkcs11_provider_finalize(struct pkcs11_provider *p)
-+{
-+	debug_f("%p refcount %d valid %d", p, p->refcount, p->valid);
-+	if (!p->valid)
-+		return;
-+	pkcs11_module_unref(p->module);
-+	p->module = NULL;
- 	p->valid = 0;
--	p->function_list = NULL;
--	dlclose(p->handle);
- }
- 
- /*
-@@ -136,15 +181,27 @@ pkcs11_provider_unref(struct pkcs11_provider *p)
- {
- 	debug_f("provider \"%s\" refcount %d", p->name, p->refcount);
- 	if (--p->refcount <= 0) {
--		if (p->valid)
--			error_f("provider \"%s\" still valid", p->name);
- 		free(p->name);
--		free(p->slotlist);
--		free(p->slotinfo);
-+		if (p->module)
-+			pkcs11_module_unref(p->module);
- 		free(p);
- 	}
- }
- 
-+/* lookup provider by module path */
-+static struct pkcs11_module *
-+pkcs11_provider_lookup_module(char *module_path)
-+{
-+	struct pkcs11_provider *p;
-+
-+	TAILQ_FOREACH(p, &pkcs11_providers, next) {
-+		debug("check %p %s (%s)", p, p->name, p->module->module_path);
-+		if (!strcmp(module_path, p->module->module_path))
-+			return (p->module);
-+	}
-+	return (NULL);
-+}
-+
- /* lookup provider by name */
- static struct pkcs11_provider *
- pkcs11_provider_lookup(char *provider_id)
-@@ -159,19 +216,55 @@ pkcs11_provider_lookup(char *provider_id)
- 	return (NULL);
- }
- 
-+int pkcs11_del_provider_by_uri(struct pkcs11_uri *);
-+
- /* unregister provider by name */
- int
- pkcs11_del_provider(char *provider_id)
-+{
-+	int rv;
-+	struct pkcs11_uri *uri;
-+
-+	debug_f("called, provider_id = %s", provider_id);
-+
-+      if (provider_id == NULL)
-+          return 0;
-+
-+	uri = pkcs11_uri_init();
-+	if (uri == NULL)
-+		fatal("Failed to init PKCS#11 URI");
-+
-+	if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
-+	    strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
-+		if (pkcs11_uri_parse(provider_id, uri) != 0)
-+			fatal("Failed to parse PKCS#11 URI");
-+	} else {
-+		uri->module_path = strdup(provider_id);
-+	}
-+
-+	rv = pkcs11_del_provider_by_uri(uri);
-+	pkcs11_uri_cleanup(uri);
-+	return rv;
-+}
-+
-+/* unregister provider by PKCS#11 URI */
-+int
-+pkcs11_del_provider_by_uri(struct pkcs11_uri *uri)
- {
- 	struct pkcs11_provider *p;
-+	int rv = -1;
-+	char *provider_uri = pkcs11_uri_get(uri);
- 
--	if ((p = pkcs11_provider_lookup(provider_id)) != NULL) {
-+	debug3_f("called with provider %s", provider_uri);
-+
-+	if ((p = pkcs11_provider_lookup(provider_uri)) != NULL) {
- 		TAILQ_REMOVE(&pkcs11_providers, p, next);
- 		pkcs11_provider_finalize(p);
- 		pkcs11_provider_unref(p);
--		return (0);
-+		rv = 0;
- 	}
--	return (-1);
-+	free(provider_uri);
-+	return rv;
- }
- 
- /* release a wrapped object */
-@@ -183,6 +276,7 @@ pkcs11_k11_free(struct pkcs11_key *k11)
- 	if (k11->provider)
- 		pkcs11_provider_unref(k11->provider);
- 	free(k11->keyid);
-+	free(k11->label);
- 	sshbuf_free(k11->keyblob);
- 	free(k11);
- }
-@@ -198,8 +292,8 @@ pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr,
- 	CK_RV			rv;
- 	int			ret = -1;
- 
--	f = p->function_list;
--	session = p->slotinfo[slotidx].session;
-+	f = p->module->function_list;
-+	session = p->module->slotinfo[slotidx].session;
- 	if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) {
- 		error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv);
- 		return (-1);
-@@ -236,14 +330,14 @@ pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si,
- 	if (si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH)
- 		verbose("Deferring PIN entry to reader keypad.");
- 	else {
--		snprintf(prompt, sizeof(prompt), "Enter PIN for '%s': ",
-+		snprintf(prompt, sizeof(prompt), "Enter PIN for '%.32s': ",
- 		    si->token.label);
--		if ((pin = read_passphrase(prompt, RP_ALLOW_EOF)) == NULL) {
-+		if ((pin = read_passphrase(prompt, RP_ALLOW_EOF|RP_ALLOW_STDIN)) == NULL) {
- 			debug_f("no pin specified");
- 			return (-1);	/* bail out */
- 		}
- 	}
--	rv = provider->function_list->C_Login(si->session, type, (u_char *)pin,
-+	rv = provider->module->function_list->C_Login(si->session, type, (u_char *)pin,
- 	    (pin != NULL) ? strlen(pin) : 0);
- 	if (pin != NULL)
- 		freezero(pin, strlen(pin));
-@@ -273,13 +367,14 @@ pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si,
- static int
- pkcs11_login(struct pkcs11_key *k11, CK_USER_TYPE type)
- {
--	if (k11 == NULL || k11->provider == NULL || !k11->provider->valid) {
-+	if (k11 == NULL || k11->provider == NULL || !k11->provider->valid ||
-+	    k11->provider->module == NULL || !k11->provider->module->valid) {
- 		error("no pkcs11 (valid) provider found");
- 		return (-1);
- 	}
- 
- 	return pkcs11_login_slot(k11->provider,
--	    &k11->provider->slotinfo[k11->slotidx], type);
-+	    &k11->provider->module->slotinfo[k11->slotidx], type);
- }
- 
- 
-@@ -295,13 +390,14 @@ pkcs11_check_obj_bool_attrib(struct pkcs11_key *k11, CK_OBJECT_HANDLE obj,
- 
- 	*val = 0;
- 
--	if (!k11->provider || !k11->provider->valid) {
-+	if (!k11->provider || !k11->provider->valid ||
-+	    !k11->provider->module || !k11->provider->module->valid) {
- 		error("no pkcs11 (valid) provider found");
- 		return (-1);
- 	}
- 
--	f = k11->provider->function_list;
--	si = &k11->provider->slotinfo[k11->slotidx];
-+	f = k11->provider->module->function_list;
-+	si = &k11->provider->module->slotinfo[k11->slotidx];
- 
- 	attr.type = type;
- 	attr.pValue = &flag;
-@@ -332,13 +428,14 @@ pkcs11_get_key(struct pkcs11_key *k11, CK_MECHANISM_TYPE mech_type)
- 	int			 always_auth = 0;
- 	int			 did_login = 0;
- 
--	if (!k11->provider || !k11->provider->valid) {
-+	if (!k11->provider || !k11->provider->valid ||
-+	    !k11->provider->module || !k11->provider->module->valid) {
- 		error("no pkcs11 (valid) provider found");
- 		return (-1);
- 	}
- 
--	f = k11->provider->function_list;
--	si = &k11->provider->slotinfo[k11->slotidx];
-+	f = k11->provider->module->function_list;
-+	si = &k11->provider->module->slotinfo[k11->slotidx];
- 
- 	if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) {
- 		if (pkcs11_login(k11, CKU_USER) < 0) {
-@@ -437,6 +534,12 @@ pkcs11_record_key(struct pkcs11_provider *provider, CK_ULONG slotidx,
- 		k11->keyid = xmalloc(k11->keyid_len);
- 		memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len);
- 	}
-+	if (keyid_attrib->ulValueLen > 0 ) {
-+		k11->label = xmalloc(keyid_attrib->ulValueLen+1);
-+		memcpy(k11->label, keyid_attrib->pValue, keyid_attrib->ulValueLen);
-+		k11->label[keyid_attrib->ulValueLen] = 0;
-+	}
-+
- 	TAILQ_INSERT_TAIL(&pkcs11_keys, k11, next);
- 
- 	return 0;
-@@ -464,6 +567,42 @@ pkcs11_lookup_key(struct sshkey *key)
- 	return found;
- }
- 
-+/*
-+ * This can't be in the ssh-pkcs11-uri, becase we can not depend on
-+ * PKCS#11 structures in ssh-agent (using client-helper communication)
-+ */
-+int
-+pkcs11_uri_write(const struct sshkey *key, FILE *f)
-+{
-+	char *p = NULL;
-+	struct pkcs11_uri uri;
-+	struct pkcs11_key *k11 = pkcs11_lookup_key(key);
-+
-+	if (k11 == NULL) {
-+		error("Failed to get ex_data for key type %d", key->type);
-+		return (-1);
-+	}
-+
-+	/* omit type -- we are looking for private-public or private-certificate pairs */
-+	uri.id = k11->keyid;
-+	uri.id_len = k11->keyid_len;
-+	uri.token = k11->provider->module->slotinfo[k11->slotidx].token.label;
-+	uri.object = k11->label;
-+	uri.module_path = k11->provider->module->module_path;
-+	uri.lib_manuf = k11->provider->module->info.manufacturerID;
-+	uri.manuf = k11->provider->module->slotinfo[k11->slotidx].token.manufacturerID;
-+	uri.serial = k11->provider->module->slotinfo[k11->slotidx].token.serialNumber;
-+
-+	p = pkcs11_uri_get(&uri);
-+	/* do not cleanup -- we do not allocate here, only reference */
-+	if (p == NULL)
-+		return -1;
-+
-+	fprintf(f, " %s", p);
-+	free(p);
-+	return 0;
-+}
-+
- #ifdef WITH_OPENSSL
- /*
-  * See:
-@@ -568,8 +707,8 @@ pkcs11_sign_rsa(struct sshkey *key,
- 		return SSH_ERR_AGENT_FAILURE;
- 	}
- 
--	f = k11->provider->function_list;
--	si = &k11->provider->slotinfo[k11->slotidx];
-+	f = k11->provider->module->function_list;
-+	si = &k11->provider->module->slotinfo[k11->slotidx];
- 
- 	if ((siglen = EVP_PKEY_size(key->pkey)) <= 0)
- 		return SSH_ERR_INVALID_ARGUMENT;
-@@ -658,8 +797,8 @@ pkcs11_sign_ecdsa(struct sshkey *key,
- 	debug3_f("sign using provider %s slotidx %lu",
- 	    k11->provider->name, (u_long)k11->slotidx);
- 
--	f = k11->provider->function_list;
--	si = &k11->provider->slotinfo[k11->slotidx];
-+	f = k11->provider->module->function_list;
-+	si = &k11->provider->module->slotinfo[k11->slotidx];
- 
- 	/* Prepare digest to be signed */
- 	if ((hashalg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1)
-@@ -743,8 +882,8 @@ pkcs11_sign_ed25519(struct sshkey *key,
- 	debug3_f("sign using provider %s slotidx %lu",
- 	    k11->provider->name, (u_long)k11->slotidx);
- 
--	f = k11->provider->function_list;
--	si = &k11->provider->slotinfo[k11->slotidx];
-+	f = k11->provider->module->function_list;
-+	si = &k11->provider->module->slotinfo[k11->slotidx];
- 
- 	xdata = xmalloc(datalen);
- 	memcpy(xdata, data, datalen);
-@@ -772,7 +911,8 @@ pkcs11_sign_ed25519(struct sshkey *key,
- 	return ret;
- }
- 
--/* remove trailing spaces */
-+/* remove trailing spaces. Note, that this does NOT guarantee the buffer
-+ * will be null terminated if there are no trailing spaces! */
- static char *
- rmspace(u_char *buf, size_t len)
- {
-@@ -804,8 +944,8 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin,
- 	CK_SESSION_HANDLE	session;
- 	int			login_required, ret;
- 
--	f = p->function_list;
--	si = &p->slotinfo[slotidx];
-+	f = p->module->function_list;
-+	si = &p->module->slotinfo[slotidx];
- 
- 	login_required = si->token.flags & CKF_LOGIN_REQUIRED;
- 
-@@ -815,9 +955,9 @@ pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin,
- 		error("pin required");
- 		return (-SSH_PKCS11_ERR_PIN_REQUIRED);
- 	}
--	if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION|
-+	if ((rv = f->C_OpenSession(p->module->slotlist[slotidx], CKF_RW_SESSION|
- 	    CKF_SERIAL_SESSION, NULL, NULL, &session)) != CKR_OK) {
--		error("C_OpenSession failed: %lu", rv);
-+		error("C_OpenSession failed for slot %lu: %lu", slotidx, rv);
- 		return (-1);
- 	}
- 	if (login_required && pin != NULL && strlen(pin) != 0) {
-@@ -854,7 +994,8 @@ static struct sshkey *
- pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
-     CK_OBJECT_HANDLE *obj)
- {
--	CK_ATTRIBUTE		 key_attr[3];
-+	CK_ATTRIBUTE		 key_attr[4];
-+	int			 nattr = 4;
- 	CK_SESSION_HANDLE	 session;
- 	CK_FUNCTION_LIST	*f = NULL;
- 	CK_RV			 rv;
-@@ -867,14 +1008,15 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 
- 	memset(&key_attr, 0, sizeof(key_attr));
- 	key_attr[0].type = CKA_ID;
--	key_attr[1].type = CKA_EC_POINT;
--	key_attr[2].type = CKA_EC_PARAMS;
-+	key_attr[1].type = CKA_LABEL;
-+	key_attr[2].type = CKA_EC_POINT;
-+	key_attr[3].type = CKA_EC_PARAMS;
- 
--	session = p->slotinfo[slotidx].session;
--	f = p->function_list;
-+	session = p->module->slotinfo[slotidx].session;
-+	f = p->module->function_list;
- 
- 	/* figure out size of the attributes */
--	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_GetAttributeValue failed: %lu", rv);
- 		return (NULL);
-@@ -885,19 +1027,19 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 	 * ensure that none of the others are zero length.
- 	 * XXX assumes CKA_ID is always first.
- 	 */
--	if (key_attr[1].ulValueLen == 0 ||
--	    key_attr[2].ulValueLen == 0) {
-+	if (key_attr[2].ulValueLen == 0 ||
-+	    key_attr[3].ulValueLen == 0) {
- 		error("invalid attribute length");
- 		return (NULL);
- 	}
- 
- 	/* allocate buffers for attributes */
--	for (i = 0; i < 3; i++)
-+	for (i = 0; i < nattr; i++)
- 		if (key_attr[i].ulValueLen > 0)
- 			key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
- 
- 	/* retrieve ID, public point and curve parameters of EC key */
--	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_GetAttributeValue failed: %lu", rv);
- 		goto fail;
-@@ -909,8 +1051,8 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 		goto fail;
- 	}
- 
--	attrp = key_attr[2].pValue;
--	group = d2i_ECPKParameters(NULL, &attrp, key_attr[2].ulValueLen);
-+	attrp = key_attr[3].pValue;
-+	group = d2i_ECPKParameters(NULL, &attrp, key_attr[3].ulValueLen);
- 	if (group == NULL) {
- 		ossl_error("d2i_ECPKParameters failed");
- 		goto fail;
-@@ -921,13 +1063,13 @@ pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 		goto fail;
- 	}
- 
--	if (key_attr[1].ulValueLen <= 2) {
-+	if (key_attr[2].ulValueLen <= 2) {
- 		error("CKA_EC_POINT too small");
- 		goto fail;
- 	}
- 
--	attrp = key_attr[1].pValue;
--	octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[1].ulValueLen);
-+	attrp = key_attr[2].pValue;
-+	octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[2].ulValueLen);
- 	if (octet == NULL) {
- 		ossl_error("d2i_ASN1_OCTET_STRING failed");
- 		goto fail;
-@@ -989,7 +1131,8 @@ static struct sshkey *
- pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
-     CK_OBJECT_HANDLE *obj)
- {
--	CK_ATTRIBUTE		 key_attr[3];
-+	CK_ATTRIBUTE		 key_attr[4];
-+	int			 nattr = 4;
- 	CK_SESSION_HANDLE	 session;
- 	CK_FUNCTION_LIST	*f = NULL;
- 	CK_RV			 rv;
-@@ -1000,14 +1143,15 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 
- 	memset(&key_attr, 0, sizeof(key_attr));
- 	key_attr[0].type = CKA_ID;
--	key_attr[1].type = CKA_MODULUS;
--	key_attr[2].type = CKA_PUBLIC_EXPONENT;
-+	key_attr[1].type = CKA_LABEL;
-+	key_attr[2].type = CKA_MODULUS;
-+	key_attr[3].type = CKA_PUBLIC_EXPONENT;
- 
--	session = p->slotinfo[slotidx].session;
--	f = p->function_list;
-+	session = p->module->slotinfo[slotidx].session;
-+	f = p->module->function_list;
- 
- 	/* figure out size of the attributes */
--	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_GetAttributeValue failed: %lu", rv);
- 		return (NULL);
-@@ -1018,19 +1162,19 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 	 * ensure that none of the others are zero length.
- 	 * XXX assumes CKA_ID is always first.
- 	 */
--	if (key_attr[1].ulValueLen == 0 ||
--	    key_attr[2].ulValueLen == 0) {
-+	if (key_attr[2].ulValueLen == 0 ||
-+	    key_attr[3].ulValueLen == 0) {
- 		error("invalid attribute length");
- 		return (NULL);
- 	}
- 
- 	/* allocate buffers for attributes */
--	for (i = 0; i < 3; i++)
-+	for (i = 0; i < nattr; i++)
- 		if (key_attr[i].ulValueLen > 0)
- 			key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
- 
- 	/* retrieve ID, modulus and public exponent of RSA key */
--	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_GetAttributeValue failed: %lu", rv);
- 		goto fail;
-@@ -1042,8 +1186,8 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 		goto fail;
- 	}
- 
--	rsa_n = BN_bin2bn(key_attr[1].pValue, key_attr[1].ulValueLen, NULL);
--	rsa_e = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
-+	rsa_n = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL);
-+	rsa_e = BN_bin2bn(key_attr[3].pValue, key_attr[3].ulValueLen, NULL);
- 	if (rsa_n == NULL || rsa_e == NULL) {
- 		error("BN_bin2bn failed");
- 		goto fail;
-@@ -1075,7 +1219,7 @@ pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 	/* success */
- 	success = 0;
- fail:
--	for (i = 0; i < 3; i++)
-+	for (i = 0; i < nattr; i++)
- 		free(key_attr[i].pValue);
- 	RSA_free(rsa);
- 	if (success != 0) {
-@@ -1090,7 +1234,8 @@ static struct sshkey *
- pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
-     CK_OBJECT_HANDLE *obj)
- {
--	CK_ATTRIBUTE		 key_attr[3];
-+	CK_ATTRIBUTE		 key_attr[4];
-+	int			 nattr = 4;
- 	CK_SESSION_HANDLE	 session;
- 	CK_FUNCTION_LIST	*f = NULL;
- 	CK_RV			 rv;
-@@ -1110,14 +1255,15 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 
- 	memset(&key_attr, 0, sizeof(key_attr));
- 	key_attr[0].type = CKA_ID;
--	key_attr[1].type = CKA_EC_POINT; /* XXX or CKA_VALUE ? */
--	key_attr[2].type = CKA_EC_PARAMS;
-+	key_attr[1].type = CKA_LABEL;
-+	key_attr[2].type = CKA_EC_POINT; /* XXX or CKA_VALUE ? */
-+	key_attr[3].type = CKA_EC_PARAMS;
- 
--	session = p->slotinfo[slotidx].session;
--	f = p->function_list;
-+	session = p->module->slotinfo[slotidx].session;
-+	f = p->module->function_list;
- 
- 	/* figure out size of the attributes */
--	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_GetAttributeValue failed: %lu", rv);
- 		return (NULL);
-@@ -1128,28 +1274,28 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 	 * ensure that none of the others are zero length.
- 	 * XXX assumes CKA_ID is always first.
- 	 */
--	if (key_attr[1].ulValueLen == 0 ||
--	    key_attr[2].ulValueLen == 0) {
-+	if (key_attr[2].ulValueLen == 0 ||
-+	    key_attr[3].ulValueLen == 0) {
- 		error("invalid attribute length");
- 		return (NULL);
- 	}
- 
- 	/* allocate buffers for attributes */
--	for (i = 0; i < 3; i++) {
-+	for (i = 0; i < nattr; i++) {
- 		if (key_attr[i].ulValueLen > 0)
- 			key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
- 	}
- 
- 	/* retrieve ID, public point and curve parameters of EC key */
--	rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
-+	rv = f->C_GetAttributeValue(session, *obj, key_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_GetAttributeValue failed: %lu", rv);
- 		goto fail;
- 	}
- 
- 	/* Expect one of the supported identifiers in CKA_EC_PARAMS */
--	d = (u_char *)key_attr[2].pValue;
--	len = key_attr[2].ulValueLen;
-+	d = (u_char *)key_attr[3].pValue;
-+	len = key_attr[3].ulValueLen;
- 	if ((len != sizeof(id1) || memcmp(d, id1, sizeof(id1)) != 0) &&
- 	    (len != sizeof(id2) || memcmp(d, id2, sizeof(id2)) != 0)) {
- 		hex = tohex(d, len);
-@@ -1161,16 +1307,16 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 	 * Expect either a raw 32 byte pubkey or an OCTET STRING with
- 	 * a 32 byte pubkey in CKA_VALUE
- 	 */
--	d = (u_char *)key_attr[1].pValue;
--	len = key_attr[1].ulValueLen;
-+	d = (u_char *)key_attr[2].pValue;
-+	len = key_attr[2].ulValueLen;
- 	if (len == ED25519_PK_SZ + 2 && d[0] == 0x04 && d[1] == ED25519_PK_SZ) {
- 		d += 2;
- 		len -= 2;
- 	}
- 	if (len != ED25519_PK_SZ) {
--		hex = tohex(key_attr[1].pValue, key_attr[1].ulValueLen);
-+		hex = tohex(key_attr[2].pValue, key_attr[2].ulValueLen);
- 		logit_f("CKA_EC_POINT invalid octet str: %s (len %lu)",
--		    hex, (u_long)key_attr[1].ulValueLen);
-+		    hex, (u_long)key_attr[2].ulValueLen);
- 		goto fail;
- 	}
- 
-@@ -1190,7 +1336,7 @@ pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 		key = NULL;
- 	}
- 	free(hex);
--	for (i = 0; i < 3; i++)
-+	for (i = 0; i < nattr; i++)
- 		free(key_attr[i].pValue);
- 	return key;
- }
-@@ -1200,7 +1346,8 @@ static int
- pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
-     CK_OBJECT_HANDLE *obj, struct sshkey **keyp, char **labelp)
- {
--	CK_ATTRIBUTE		 cert_attr[3];
-+	CK_ATTRIBUTE		 cert_attr[4];
-+	int			 nattr = 4;
- 	CK_SESSION_HANDLE	 session;
- 	CK_FUNCTION_LIST	*f = NULL;
- 	CK_RV			 rv;
-@@ -1226,14 +1373,15 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 
- 	memset(&cert_attr, 0, sizeof(cert_attr));
- 	cert_attr[0].type = CKA_ID;
--	cert_attr[1].type = CKA_SUBJECT;
--	cert_attr[2].type = CKA_VALUE;
-+	cert_attr[1].type = CKA_LABEL;
-+	cert_attr[2].type = CKA_SUBJECT;
-+	cert_attr[3].type = CKA_VALUE;
- 
--	session = p->slotinfo[slotidx].session;
--	f = p->function_list;
-+	session = p->module->slotinfo[slotidx].session;
-+	f = p->module->function_list;
- 
- 	/* figure out size of the attributes */
--	rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
-+	rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_GetAttributeValue failed: %lu", rv);
- 		return -1;
-@@ -1245,18 +1393,19 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 	 * XXX assumes CKA_ID is always first.
- 	 */
- 	if (cert_attr[1].ulValueLen == 0 ||
--	    cert_attr[2].ulValueLen == 0) {
-+	    cert_attr[2].ulValueLen == 0 ||
-+	    cert_attr[3].ulValueLen == 0) {
- 		error("invalid attribute length");
- 		return -1;
- 	}
- 
- 	/* allocate buffers for attributes */
--	for (i = 0; i < 3; i++)
-+	for (i = 0; i < nattr; i++)
- 		if (cert_attr[i].ulValueLen > 0)
- 			cert_attr[i].pValue = xcalloc(1, cert_attr[i].ulValueLen);
- 
- 	/* retrieve ID, subject and value of certificate */
--	rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3);
-+	rv = f->C_GetAttributeValue(session, *obj, cert_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_GetAttributeValue failed: %lu", rv);
- 		goto out;
-@@ -1270,8 +1419,8 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 		subject = xstrdup("invalid subject");
- 	X509_NAME_free(x509_name);
- 
--	cp = cert_attr[2].pValue;
--	if ((x509 = d2i_X509(NULL, &cp, cert_attr[2].ulValueLen)) == NULL) {
-+	cp = cert_attr[3].pValue;
-+	if ((x509 = d2i_X509(NULL, &cp, cert_attr[3].ulValueLen)) == NULL) {
- 		error("d2i_x509 failed");
- 		goto out;
- 	}
-@@ -1381,7 +1530,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
- 		goto out;
- 	}
-  out:
--	for (i = 0; i < 3; i++)
-+	for (i = 0; i < nattr; i++)
- 		free(cert_attr[i].pValue);
- 	X509_free(x509);
- 	RSA_free(rsa);
-@@ -1424,11 +1573,12 @@ note_key(struct pkcs11_provider *p, CK_ULONG slotidx, const char *context,
-  */
- static int
- pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
--    struct sshkey ***keysp, char ***labelsp, int *nkeys)
-+    struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
- {
- 	struct sshkey		*key = NULL;
- 	CK_OBJECT_CLASS		 key_class;
--	CK_ATTRIBUTE		 key_attr[1];
-+	CK_ATTRIBUTE		 key_attr[3];
-+	int			 nattr = 1;
- 	CK_SESSION_HANDLE	 session;
- 	CK_FUNCTION_LIST	*f = NULL;
- 	CK_RV			 rv;
-@@ -1445,10 +1595,23 @@ pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
- 	key_attr[0].pValue = &key_class;
- 	key_attr[0].ulValueLen = sizeof(key_class);
- 
--	session = p->slotinfo[slotidx].session;
--	f = p->function_list;
-+	if (uri->id != NULL) {
-+		key_attr[nattr].type = CKA_ID;
-+		key_attr[nattr].pValue = uri->id;
-+		key_attr[nattr].ulValueLen = uri->id_len;
-+		nattr++;
-+	}
-+	if (uri->object != NULL) {
-+		key_attr[nattr].type = CKA_LABEL;
-+		key_attr[nattr].pValue = uri->object;
-+		key_attr[nattr].ulValueLen = strlen(uri->object);
-+		nattr++;
-+	}
-+
-+	session = p->module->slotinfo[slotidx].session;
-+	f = p->module->function_list;
- 
--	rv = f->C_FindObjectsInit(session, key_attr, 1);
-+	rv = f->C_FindObjectsInit(session, key_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_FindObjectsInit failed: %lu", rv);
- 		goto fail;
-@@ -1521,6 +1684,13 @@ fail:
- 
- 	return (ret);
- }
-+#else
-+static int
-+pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx,
-+    struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
-+{
-+	return 0;
-+}
- #endif /* WITH_OPENSSL */
- 
- /*
-@@ -1530,11 +1700,12 @@ fail:
-  */
- static int
- pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
--    struct sshkey ***keysp, char ***labelsp, int *nkeys)
-+    struct sshkey ***keysp, char ***labelsp, int *nkeys, struct pkcs11_uri *uri)
- {
- 	struct sshkey		*key = NULL;
- 	CK_OBJECT_CLASS		 key_class;
--	CK_ATTRIBUTE		 key_attr[2];
-+	CK_ATTRIBUTE		 key_attr[3];
-+	int			 nattr = 1;
- 	CK_SESSION_HANDLE	 session;
- 	CK_FUNCTION_LIST	*f = NULL;
- 	CK_RV			 rv;
-@@ -1550,10 +1721,23 @@ pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
- 	key_attr[0].pValue = &key_class;
- 	key_attr[0].ulValueLen = sizeof(key_class);
- 
--	session = p->slotinfo[slotidx].session;
--	f = p->function_list;
-+	if (uri->id != NULL) {
-+		key_attr[nattr].type = CKA_ID;
-+		key_attr[nattr].pValue = uri->id;
-+		key_attr[nattr].ulValueLen = uri->id_len;
-+		nattr++;
-+	}
-+	if (uri->object != NULL) {
-+		key_attr[nattr].type = CKA_LABEL;
-+		key_attr[nattr].pValue = uri->object;
-+		key_attr[nattr].ulValueLen = strlen(uri->object);
-+		nattr++;
-+	}
-+
-+	session = p->module->slotinfo[slotidx].session;
-+	f = p->module->function_list;
- 
--	rv = f->C_FindObjectsInit(session, key_attr, 1);
-+	rv = f->C_FindObjectsInit(session, key_attr, nattr);
- 	if (rv != CKR_OK) {
- 		error("C_FindObjectsInit failed: %lu", rv);
- 		goto fail;
-@@ -1841,16 +2025,10 @@ pkcs11_ecdsa_generate_private_key(struct pkcs11_provider *p, CK_ULONG slotidx,
- }
- #endif /* WITH_PKCS11_KEYGEN */
- 
--/*
-- * register a new provider, fails if provider already exists. if
-- * keyp is provided, fetch keys.
-- */
- static int
--pkcs11_register_provider(char *provider_id, char *pin,
--    struct sshkey ***keyp, char ***labelsp,
--    struct pkcs11_provider **providerp, CK_ULONG user)
-+pkcs11_initialize_provider(struct pkcs11_uri *uri, struct pkcs11_provider **providerp)
- {
--	int nkeys, need_finalize = 0;
-+	int need_finalize = 0;
- 	int ret = -1;
- 	struct pkcs11_provider *p = NULL;
- 	void *handle = NULL;
-@@ -1859,128 +2037,126 @@ pkcs11_register_provider(char *provider_id, char *pin,
- 	CK_FUNCTION_LIST *f = NULL;
- 	CK_TOKEN_INFO *token;
- 	CK_ULONG i;
-+	char *provider_module = NULL;
-+	struct pkcs11_module *m = NULL;
-+
-+	/* if no provider specified, fallback to p11-kit */
-+	if (uri->module_path == NULL) {
-+#ifdef PKCS11_DEFAULT_PROVIDER
-+		provider_module = strdup(PKCS11_DEFAULT_PROVIDER);
-+#else
-+		error_f("No module path provided");
-+ 		goto fail;
-+#endif
-+	} else {
-+		provider_module = strdup(uri->module_path);
-+	}
-+	p = xcalloc(1, sizeof(*p));
-+	p->name = pkcs11_uri_get(uri);
- 
--	if (providerp == NULL)
--		goto fail;
--	*providerp = NULL;
--
--	if (keyp != NULL)
--		*keyp = NULL;
--	if (labelsp != NULL)
--		*labelsp = NULL;
--
--	if (pkcs11_provider_lookup(provider_id) != NULL) {
--		debug_f("provider already registered: %s", provider_id);
-+	if (lib_contains_symbol(provider_module, "C_GetFunctionList") != 0) {
-+		error("provider %s is not a PKCS11 library", provider_module);
- 		goto fail;
- 	}
--	if (lib_contains_symbol(provider_id, "C_GetFunctionList") != 0) {
--		error("provider %s is not a PKCS11 library", provider_id);
--		goto fail;
-+	if ((m = pkcs11_provider_lookup_module(provider_module)) != NULL
-+	   && m->valid) {
-+		debug_f("provider module already initialized: %s", provider_module);
-+		free(provider_module);
-+		/* Skip the initialization of PKCS#11 module */
-+		m->refcount++;
-+		p->module = m;
-+		p->valid = 1;
-+		TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
-+		p->refcount++;	/* add to provider list */
-+		*providerp = p;
-+		return 0;
-+	} else {
-+		m = xcalloc(1, sizeof(*m));
-+		p->module = m;
-+		m->refcount++;
- 	}
-+
- 	/* open shared pkcs11-library */
--	if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) {
--		error("dlopen %s failed: %s", provider_id, dlerror());
-+	if ((handle = dlopen(provider_module, RTLD_NOW)) == NULL) {
-+		error("dlopen %s failed: %s", provider_module, dlerror());
- 		goto fail;
- 	}
- 	if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL)
- 		fatal("dlsym(C_GetFunctionList) failed: %s", dlerror());
--	p = xcalloc(1, sizeof(*p));
--	p->name = xstrdup(provider_id);
--	p->handle = handle;
--	/* set up the pkcs11 callbacks */
-+	p->module->handle = handle;
-+	/* setup the pkcs11 callbacks */
- 	if ((rv = (*getfunctionlist)(&f)) != CKR_OK) {
- 		error("C_GetFunctionList for provider %s failed: %lu",
--		    provider_id, rv);
-+		    provider_module, rv);
- 		goto fail;
- 	}
--	p->function_list = f;
-+	m->function_list = f;
- 	if ((rv = f->C_Initialize(NULL)) != CKR_OK) {
- 		error("C_Initialize for provider %s failed: %lu",
--		    provider_id, rv);
-+		    provider_module, rv);
- 		goto fail;
- 	}
- 	need_finalize = 1;
--	if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) {
-+	if ((rv = f->C_GetInfo(&m->info)) != CKR_OK) {
- 		error("C_GetInfo for provider %s failed: %lu",
--		    provider_id, rv);
-+		    provider_module, rv);
- 		goto fail;
- 	}
--	debug("provider %s: manufacturerID <%.*s> cryptokiVersion %d.%d"
--	    " libraryDescription <%.*s> libraryVersion %d.%d",
--	    provider_id,
--	    RMSPACE(p->info.manufacturerID),
--	    p->info.cryptokiVersion.major,
--	    p->info.cryptokiVersion.minor,
--	    RMSPACE(p->info.libraryDescription),
--	    p->info.libraryVersion.major,
--	    p->info.libraryVersion.minor);
--	if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) {
-+	rmspace(m->info.manufacturerID, sizeof(m->info.manufacturerID));
-+	if (uri->lib_manuf != NULL &&
-+	    strncmp(uri->lib_manuf, m->info.manufacturerID, 32)) {
-+		debug_f("Skipping provider %s not matching library_manufacturer",
-+		    m->info.manufacturerID);
-+ 		goto fail;
-+ 	}
-+	rmspace(m->info.libraryDescription, sizeof(m->info.libraryDescription));
-+	debug("provider %s: manufacturerID <%.32s> cryptokiVersion %d.%d"
-+	    " libraryDescription <%.32s> libraryVersion %d.%d",
-+	    provider_module,
-+	    m->info.manufacturerID,
-+	    m->info.cryptokiVersion.major,
-+	    m->info.cryptokiVersion.minor,
-+	    m->info.libraryDescription,
-+	    m->info.libraryVersion.major,
-+	    m->info.libraryVersion.minor);
-+
-+	if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &m->nslots)) != CKR_OK) {
- 		error("C_GetSlotList failed: %lu", rv);
- 		goto fail;
- 	}
--	if (p->nslots == 0) {
--		debug_f("provider %s returned no slots", provider_id);
-+	if (m->nslots == 0) {
-+		debug_f("provider %s returned no slots", provider_module);
- 		ret = -SSH_PKCS11_ERR_NO_SLOTS;
- 		goto fail;
- 	}
--	p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID));
--	if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots))
-+	m->slotlist = xcalloc(m->nslots, sizeof(CK_SLOT_ID));
-+	if ((rv = f->C_GetSlotList(CK_TRUE, m->slotlist, &m->nslots))
- 	    != CKR_OK) {
- 		error("C_GetSlotList for provider %s failed: %lu",
--		    provider_id, rv);
-+		    provider_module, rv);
- 		goto fail;
- 	}
--	p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo));
-+	m->slotinfo = xcalloc(m->nslots, sizeof(struct pkcs11_slotinfo));
- 	p->valid = 1;
--	nkeys = 0;
--	for (i = 0; i < p->nslots; i++) {
--		token = &p->slotinfo[i].token;
--		if ((rv = f->C_GetTokenInfo(p->slotlist[i], token))
-+	m->valid = 1;
-+	for (i = 0; i < m->nslots; i++) {
-+		token = &m->slotinfo[i].token;
-+		if ((rv = f->C_GetTokenInfo(m->slotlist[i], token))
- 		    != CKR_OK) {
- 			error("C_GetTokenInfo for provider %s slot %lu "
--			    "failed: %lu", provider_id, (u_long)i, rv);
--			continue;
--		}
--		if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) {
--			debug2_f("ignoring uninitialised token in "
--			    "provider %s slot %lu", provider_id, (u_long)i);
-+			    "failed: %lu", provider_module, (u_long)i, rv);
- 			continue;
- 		}
- 		debug("provider %s slot %lu: label <%.*s> "
- 		    "manufacturerID <%.*s> model <%.*s> serial <%.*s> "
- 		    "flags 0x%lx",
--		    provider_id, (unsigned long)i,
-+		    provider_module, (unsigned long)i,
- 		    RMSPACE(token->label), RMSPACE(token->manufacturerID),
- 		    RMSPACE(token->model), RMSPACE(token->serialNumber),
- 		    token->flags);
--		/*
--		 * open session, login with pin and retrieve public
--		 * keys (if keyp is provided)
--		 */
--		if ((ret = pkcs11_open_session(p, i, pin, user)) != 0 ||
--		    keyp == NULL)
--			continue;
--		pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys);
--#ifdef WITH_OPENSSL
--		pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
--#endif
--		if (nkeys == 0 && !p->slotinfo[i].logged_in &&
--		    pkcs11_interactive) {
--			/*
--			 * Some tokens require login before they will
--			 * expose keys.
--			 */
--			if (pkcs11_login_slot(p, &p->slotinfo[i],
--			    CKU_USER) < 0) {
--				error("login failed");
--				continue;
--			}
--			pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys);
--#ifdef WITH_OPENSSL
--			pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys);
--#endif
--		}
- 	}
-+	m->module_path = provider_module;
-+	provider_module = NULL;
- 
- 	/* now owned by caller */
- 	*providerp = p;
-@@ -1988,21 +2164,22 @@ pkcs11_register_provider(char *provider_id, char *pin,
- 	TAILQ_INSERT_TAIL(&pkcs11_providers, p, next);
- 	p->refcount++;	/* add to provider list */
- 
--	return (nkeys);
-+	return 0;
- fail:
- 	if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK)
- 		error("C_Finalize for provider %s failed: %lu",
--		    provider_id, rv);
-+		    provider_module, rv);
-+	free(provider_module);
-+	if (m) {
-+		free(m->slotlist);
-+		free(m);
-+	}
- 	if (p) {
- 		free(p->name);
--		free(p->slotlist);
--		free(p->slotinfo);
- 		free(p);
- 	}
- 	if (handle)
- 		dlclose(handle);
--	if (ret > 0)
--		ret = -1;
- 	return (ret);
- }
- 
-@@ -2038,18 +2215,163 @@ pkcs11_terminate(void)
- }
- 
- /*
-- * register a new provider and get number of keys hold by the token,
-- * fails if provider already exists
-+ * register a new provider, fails if provider already exists. if
-+ * keyp is provided, fetch keys.
-  */
-+static int
-+pkcs11_register_provider_by_uri(struct pkcs11_uri *uri, char *pin,
-+    struct sshkey ***keyp, char ***labelsp, struct pkcs11_provider **providerp,
-+    CK_ULONG user)
-+{
-+	int nkeys;
-+	int ret = -1;
-+	struct pkcs11_provider *p = NULL;
-+	CK_ULONG i;
-+	CK_TOKEN_INFO *token;
-+	char *provider_uri = NULL;
-+
-+	if (providerp == NULL)
-+		goto fail;
-+	*providerp = NULL;
-+
-+	if (keyp != NULL)
-+		*keyp = NULL;
-+
-+	if ((ret = pkcs11_initialize_provider(uri, &p)) != 0) {
-+		goto fail;
-+	}
-+
-+	provider_uri = pkcs11_uri_get(uri);
-+	if (pin == NULL && uri->pin != NULL) {
-+		pin = uri->pin;
-+	}
-+	nkeys = 0;
-+	for (i = 0; i < p->module->nslots; i++) {
-+		token = &p->module->slotinfo[i].token;
-+		if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) {
-+			debug2_f("ignoring uninitialised token in "
-+			    "provider %s slot %lu", provider_uri, (u_long)i);
-+			continue;
-+		}
-+		if (uri->token != NULL &&
-+		    strncmp(token->label, uri->token, 32) != 0) {
-+			debug2_f("ignoring token not matching label (%.32s) "
-+			    "specified by PKCS#11 URI in slot %lu",
-+			    token->label, (unsigned long)i);
-+			continue;
-+		}
-+		if (uri->manuf != NULL &&
-+		    strncmp(token->manufacturerID, uri->manuf, 32) != 0) {
-+			debug2_f("ignoring token not matching requrested "
-+			    "manufacturerID (%.32s) specified by PKCS#11 URI in "
-+			    "slot %lu", token->manufacturerID, (unsigned long)i);
-+			continue;
-+		}
-+		if (uri->serial != NULL &&
-+		    strncmp(token->serialNumber, uri->serial, 16) != 0) {
-+			debug2_f("ignoring token not matching requrested "
-+			    "serialNumber (%s) specified by PKCS#11 URI in "
-+			    "slot %lu", token->serialNumber, (unsigned long)i);
-+			continue;
-+		}
-+		debug("provider %s slot %lu: label <%.32s> manufacturerID <%.32s> "
-+		    "model <%.16s> serial <%.16s> flags 0x%lx",
-+		    provider_uri, (unsigned long)i,
-+		    token->label, token->manufacturerID, token->model,
-+		    token->serialNumber, token->flags);
-+		/*
-+		 * open session if not yet opened, login with pin and
-+		 * retrieve public keys (if keyp is provided)
-+		 */
-+		if ((p->module->slotinfo[i].session != 0 ||
-+		    (ret = pkcs11_open_session(p, i, pin, user)) != 0) && /* ??? */
-+		    keyp == NULL)
-+			continue;
-+		pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
-+		pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
-+		if (nkeys == 0 && !p->module->slotinfo[i].logged_in &&
-+		    pkcs11_interactive) {
-+			/*
-+			 * Some tokens require login before they will
-+			 * expose keys.
-+			 */
-+			debug3_f("Trying to login as there were no keys found");
-+			if (pkcs11_login_slot(p, &p->module->slotinfo[i],
-+			    CKU_USER) < 0) {
-+				error("login failed");
-+				continue;
-+			}
-+			pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
-+			pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
-+		}
-+		if (nkeys == 0 && uri->object != NULL) {
-+			debug3_f("No keys found. Retrying without label (%.32s) ",
-+			    uri->object);
-+			/* Try once more without the label filter */
-+			char *label = uri->object;
-+			uri->object = NULL; /* XXX clone uri? */
-+			pkcs11_fetch_keys(p, i, keyp, labelsp, &nkeys, uri);
-+			pkcs11_fetch_certs(p, i, keyp, labelsp, &nkeys, uri);
-+			uri->object = label;
-+		}
-+	}
-+	pin = NULL; /* Will be cleaned up with URI */
-+
-+	/* now owned by caller */
-+	*providerp = p;
-+
-+	free(provider_uri);
-+	return (nkeys);
-+ fail:
-+	if (p) {
-+		TAILQ_REMOVE(&pkcs11_providers, p, next);
-+	     	pkcs11_provider_unref(p);
-+	}
-+	if (ret > 0)
-+		ret = -1;
-+	return (ret);
-+}
-+
-+#ifdef WITH_PKCS11_KEYGEN
-+static int
-+pkcs11_register_provider(char *provider_id, char *pin, struct sshkey ***keyp,
-+    char ***labelsp, struct pkcs11_provider **providerp, CK_ULONG user)
-+{
-+	struct pkcs11_uri *uri = NULL;
-+	int r;
-+
-+	debug_f("called, provider_id = %s", provider_id);
-+
-+	uri = pkcs11_uri_init();
-+	if (uri == NULL)
-+		fatal("failed to init PKCS#11 URI");
-+
-+	if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
-+	    strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
-+		if (pkcs11_uri_parse(provider_id, uri) != 0)
-+			fatal("Failed to parse PKCS#11 URI");
-+	} else {
-+		uri->module_path = strdup(provider_id);
-+	}
-+
-+	r = pkcs11_register_provider_by_uri(uri, pin, keyp, labelsp, providerp, user);
-+	pkcs11_uri_cleanup(uri);
-+
-+	return r;
-+}
-+#endif
-+
- int
--pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp,
--    char ***labelsp)
-+pkcs11_add_provider_by_uri(struct pkcs11_uri *uri, char *pin,
-+    struct sshkey ***keyp, char ***labelsp)
- {
- 	struct pkcs11_provider *p = NULL;
- 	int nkeys;
-+	char *provider_uri = pkcs11_uri_get(uri);
- 
--	nkeys = pkcs11_register_provider(provider_id, pin, keyp, labelsp,
--	    &p, CKU_USER);
-+	debug_f("called, provider_uri = %s", provider_uri);
-+
-+	nkeys = pkcs11_register_provider_by_uri(uri, pin, keyp, labelsp, &p, CKU_USER);
- 
- 	/* no keys found or some other error, de-register provider */
- 	if (nkeys <= 0 && p != NULL) {
-@@ -2058,11 +2380,38 @@ pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp,
- 		pkcs11_provider_unref(p);
- 	}
- 	if (nkeys == 0)
--		debug_f("provider %s returned no keys", provider_id);
-+		debug_f("provider %s returned no keys", provider_uri);
- 
-+	free(provider_uri);
- 	return (nkeys);
- }
- 
-+int
-+pkcs11_add_provider(char *provider_id, char *pin,
-+    struct sshkey ***keyp, char ***labelsp)
-+{
-+	struct pkcs11_uri *uri;
-+	int nkeys;
-+
-+	uri = pkcs11_uri_init();
-+	if (uri == NULL)
-+		fatal("Failed to init PKCS#11 URI");
-+
-+	if (strlen(provider_id) >= strlen(PKCS11_URI_SCHEME) &&
-+	    strncmp(provider_id, PKCS11_URI_SCHEME, strlen(PKCS11_URI_SCHEME)) == 0) {
-+		if (pkcs11_uri_parse(provider_id, uri) != 0)
-+			fatal("Failed to parse PKCS#11 URI");
-+	} else {
-+		uri->module_path = strdup(provider_id);
-+	}
-+
-+	nkeys = pkcs11_add_provider_by_uri(uri, pin, keyp, labelsp);
-+	pkcs11_uri_cleanup(uri);
-+
-+ 	return (nkeys);
-+}
-+
-+
- int
- pkcs11_sign(struct sshkey *key,
-     u_char **sigp, size_t *lenp,
-diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h
-index 1d0277a6d..13459644c 100644
---- a/ssh-pkcs11.h
-+++ b/ssh-pkcs11.h
-@@ -24,12 +24,16 @@
- #define	SSH_PKCS11_ERR_PIN_REQUIRED		4
- #define	SSH_PKCS11_ERR_PIN_LOCKED		5
- 
-+#include "ssh-pkcs11-uri.h"
-+
- struct sshkey;
- 
- int	pkcs11_init(int);
- void	pkcs11_terminate(void);
- int	pkcs11_add_provider(char *, char *, struct sshkey ***, char ***);
-+int	pkcs11_add_provider_by_uri(struct pkcs11_uri *, char *, struct sshkey ***, char ***);
- int	pkcs11_del_provider(char *);
-+int	pkcs11_uri_write(const struct sshkey *, FILE *);
- int	pkcs11_sign(struct sshkey *, u_char **, size_t *,
- 	    const u_char *, size_t, const char *, const char *,
- 	    const char *, u_int);
-diff --git a/ssh.c b/ssh.c
-index 4a17c0a7d..b41ae82d2 100644
---- a/ssh.c
-+++ b/ssh.c
-@@ -862,6 +862,14 @@ main(int ac, char **av)
- 			options.gss_deleg_creds = 1;
- 			break;
- 		case 'i':
-+#ifdef ENABLE_PKCS11
-+			if (strlen(optarg) >= strlen(PKCS11_URI_SCHEME) &&
-+			    strncmp(optarg, PKCS11_URI_SCHEME,
-+			    strlen(PKCS11_URI_SCHEME)) == 0) {
-+				add_identity_file(&options, NULL, optarg, 1);
-+				break;
-+			}
-+#endif
- 			p = tilde_expand_filename(optarg, getuid());
- 			if (stat(p, &st) == -1)
- 				fprintf(stderr, "Warning: Identity file %s "
-@@ -1838,6 +1846,7 @@ main(int ac, char **av)
- 
- #ifdef ENABLE_PKCS11
- 	(void)pkcs11_del_provider(options.pkcs11_provider);
-+	pkcs11_terminate();
- #endif
- 
-  skip_connect:
-@@ -2349,6 +2358,45 @@ ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
- 	    options.escape_char : SSH_ESCAPECHAR_NONE, id);
- }
- 
-+#ifdef ENABLE_PKCS11
-+static void
-+load_pkcs11_identity(char *pkcs11_uri, char *identity_files[],
-+    struct sshkey *identity_keys[], int *n_ids)
-+{
-+	int nkeys, i;
-+	struct sshkey **keys;
-+	struct pkcs11_uri *uri;
-+
-+	debug("identity file '%s' from pkcs#11", pkcs11_uri);
-+	uri = pkcs11_uri_init();
-+	if (uri == NULL)
-+		fatal("Failed to init PKCS#11 URI");
-+
-+	if (pkcs11_uri_parse(pkcs11_uri, uri) != 0)
-+	fatal("Failed to parse PKCS#11 URI %s", pkcs11_uri);
-+
-+	/* we need to merge URI and provider together */
-+	if (options.pkcs11_provider != NULL && uri->module_path == NULL)
-+		uri->module_path = strdup(options.pkcs11_provider);
-+
-+	if (options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
-+	    (nkeys = pkcs11_add_provider_by_uri(uri, NULL, &keys, NULL)) > 0) {
-+		for (i = 0; i < nkeys; i++) {
-+			if (*n_ids >= SSH_MAX_IDENTITY_FILES) {
-+				sshkey_free(keys[i]);
-+				continue;
-+			}
-+			identity_keys[*n_ids] = keys[i];
-+			identity_files[*n_ids] = pkcs11_uri_get(uri);
-+			(*n_ids)++;
-+		}
-+		free(keys);
-+	}
-+
-+	pkcs11_uri_cleanup(uri);
-+}
-+#endif /* ENABLE_PKCS11 */
-+
- /* Loads all IdentityFile and CertificateFile keys */
- static void
- load_public_identity_files(const struct ssh_conn_info *cinfo)
-@@ -2363,11 +2411,6 @@ load_public_identity_files(const struct ssh_conn_info *cinfo)
- 	char *certificate_files[SSH_MAX_CERTIFICATE_FILES];
- 	struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES];
- 	int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES];
--#ifdef ENABLE_PKCS11
--	struct sshkey **keys = NULL;
--	char **comments = NULL;
--	int nkeys;
--#endif /* PKCS11 */
- 
- 	n_ids = n_certs = 0;
- 	memset(identity_files, 0, sizeof(identity_files));
-@@ -2380,33 +2423,46 @@ load_public_identity_files(const struct ssh_conn_info *cinfo)
- 	    sizeof(certificate_file_userprovided));
- 
- #ifdef ENABLE_PKCS11
--	if (options.pkcs11_provider != NULL &&
--	    options.num_identity_files < SSH_MAX_IDENTITY_FILES &&
--	    (pkcs11_init(!options.batch_mode) == 0) &&
--	    (nkeys = pkcs11_add_provider(options.pkcs11_provider, NULL,
--	    &keys, &comments)) > 0) {
--		for (i = 0; i < nkeys; i++) {
--			if (n_ids >= SSH_MAX_IDENTITY_FILES) {
--				sshkey_free(keys[i]);
--				free(comments[i]);
--				continue;
--			}
--			identity_keys[n_ids] = keys[i];
--			identity_files[n_ids] = comments[i]; /* transferred */
--			n_ids++;
--		}
--		free(keys);
--		free(comments);
-+	/* handle fallback from PKCS11Provider option */
-+	pkcs11_init(!options.batch_mode);
-+
-+	if (options.pkcs11_provider != NULL) {
-+		struct pkcs11_uri *uri;
-+
-+		uri = pkcs11_uri_init();
-+		if (uri == NULL)
-+			fatal("Failed to init PKCS#11 URI");
-+
-+		/* Construct simple PKCS#11 URI to simplify access */
-+		uri->module_path = strdup(options.pkcs11_provider);
-+
-+		/* Add it as any other IdentityFile */
-+		cp = pkcs11_uri_get(uri);
-+		add_identity_file(&options, NULL, cp, 1);
-+		free(cp);
-+
-+		pkcs11_uri_cleanup(uri);
- 	}
- #endif /* ENABLE_PKCS11 */
- 	for (i = 0; i < options.num_identity_files; i++) {
-+		char *name = options.identity_files[i];
- 		if (n_ids >= SSH_MAX_IDENTITY_FILES ||
--		    strcasecmp(options.identity_files[i], "none") == 0) {
-+		    strcasecmp(name, "none") == 0) {
- 			free(options.identity_files[i]);
- 			options.identity_files[i] = NULL;
- 			continue;
- 		}
--		cp = tilde_expand_filename(options.identity_files[i], getuid());
-+#ifdef ENABLE_PKCS11
-+		if (strlen(name) >= strlen(PKCS11_URI_SCHEME) &&
-+		    strncmp(name, PKCS11_URI_SCHEME,
-+		    strlen(PKCS11_URI_SCHEME)) == 0) {
-+			load_pkcs11_identity(name, identity_files,
-+			    identity_keys, &n_ids);
-+			free(options.identity_files[i]);
-+			continue;
-+		}
-+#endif /* ENABLE_PKCS11 */
-+		cp = tilde_expand_filename(name, getuid());
- 		filename = default_client_percent_dollar_expand(cp, cinfo);
- 		free(cp);
- 		check_load(sshkey_load_public(filename, &public, NULL),
-diff --git a/ssh_config.5 b/ssh_config.5
-index ab98c172f..699b3f67d 100644
---- a/ssh_config.5
-+++ b/ssh_config.5
-@@ -1262,6 +1262,21 @@ may also be used in conjunction with
- .Cm CertificateFile
- in order to provide any certificate also needed for authentication with
- the identity.
-+.Pp
-+The authentication identity can be also specified in a form of PKCS#11 URI
-+starting with a string
-+.Cm pkcs11: .
-+There is supported a subset of the PKCS#11 URI as defined
-+in RFC 7512 (implemented path arguments
-+.Cm id ,
-+.Cm manufacturer ,
-+.Cm object ,
-+.Cm token
-+and query arguments
-+.Cm module-path
-+and
-+.Cm pin-value
-+). The URI can not be in quotes.
- .It Cm IgnoreUnknown
- Specifies a pattern-list of unknown options to be ignored if they are
- encountered in configuration parsing.
-diff --git a/sshkey.c b/sshkey.c
-index 5225eabf2..583f6421d 100644
---- a/sshkey.c
-+++ b/sshkey.c
-@@ -856,8 +856,10 @@ sshkey_free_contents(struct sshkey *k)
- 
- 	if (k == NULL)
- 		return;
-+#ifdef ENABLE_PKCS11
- 	if ((k->flags & SSHKEY_FLAG_EXT) != 0)
- 		pkcs11_key_free(k);
-+#endif
- 	if ((impl = sshkey_impl_from_type(k->type)) != NULL &&
- 	    impl->funcs->cleanup != NULL)
- 		impl->funcs->cleanup(k);
-@@ -2304,9 +2306,11 @@ sshkey_sign(struct sshkey *key,
- 	if (sshkey_is_sk(key)) {
- 		r = sshsk_sign(sk_provider, key, sigp, lenp, data,
- 		    datalen, compat, sk_pin);
-+#ifdef ENABLE_PKCS11
- 	} else if ((key->flags & SSHKEY_FLAG_EXT) != 0) {
- 		r = pkcs11_sign(key, sigp, lenp, data, datalen,
- 		    alg, sk_provider, sk_pin, compat);
-+#endif
- 	} else {
- 		if (impl->funcs->sign == NULL)
- 			r = SSH_ERR_SIGN_ALG_UNSUPPORTED;
--- 
-2.53.0
-

diff --git a/0054-gssapi-tests.patch b/0054-gssapi-tests.patch
deleted file mode 100644
index 0f2b554..0000000
--- a/0054-gssapi-tests.patch
+++ /dev/null
@@ -1,240 +0,0 @@
-diff --git a/regress/Makefile b/regress/Makefile
-index cbe7a9610..eb9f7ad00 100644
---- a/regress/Makefile
-+++ b/regress/Makefile
-@@ -127,6 +127,7 @@ INTEROP_TESTS+=	dropbear-ciphers dropbear-kex dropbear-server
- #INTEROP_TESTS+=ssh-com ssh-com-client ssh-com-keygen ssh-com-sftp
- 
- EXTRA_TESTS=	agent-pkcs11
-+EXTRA_TESTS+=	gss-auth gss-kex
- #EXTRA_TESTS+= 	cipher-speed
- 
- USERNAME=		${LOGNAME}
-diff --git a/regress/gss-kex.sh b/regress/gss-kex.sh
-new file mode 100644
-index 000000000..17a3bf6f1
---- /dev/null
-+++ b/regress/gss-kex.sh
-@@ -0,0 +1,222 @@
-+tid="GSS-API Key Exchange"
-+
-+# Skip the test if GSSAPI support is not configured
-+if ! grep -E '^#define GSSAPI' "$BUILDDIR/config.h" >/dev/null 2>&1; then
-+    skip "GSSAPI not enabled"
-+fi
-+
-+# We test with MIT Kerberos KDC, skip if not installed
-+if ! which krb5kdc >/dev/null 2>&1; then
-+    skip "MIT Kerberos KDC not installed"
-+fi
-+
-+# The test needs nss_wrapper to emulate gethostname() and /etc/hosts,
-+# we skip if the shared library is not installed
-+nss_wrapper="libnss_wrapper.so"
-+if ! ldconfig -p | grep "$nss_wrapper" >/dev/null 2>&1; then
-+    skip "$nss_wrapper not installed"
-+fi
-+
-+# Set up the username of the SSH client
-+client="$LOGNAME"
-+if [ "x$client" = "x" ]; then
-+	client="$(whoami)"
-+fi
-+
-+# Set up SSHD and KDC hostnames and resolve both to localhost
-+sshd_hostname="sshd.example.org"
-+kdc_hostname="kdc.example.org"
-+kdc_port=2088
-+hosts="$OBJ/hosts"
-+echo "127.0.0.1 $sshd_hostname $kdc_hostname" > "$hosts"
-+
-+# Set up a directory to store Kerberos data
-+gssdir="$OBJ/gss"
-+mkdir -p "$gssdir"
-+export KRB5CCNAME="$gssdir/cc"
-+export KRB5_CONFIG="$gssdir/krb5.conf"
-+export KRB5_KDC_PROFILE="$gssdir/kdc.conf"
-+export KRB5_KTNAME="$gssdir/ssh.keytab"
-+export KRB5RCACHETYPE="none"
-+kdc_pidfile="$gssdir/pid"
-+
-+# Configure Kerberos
-+cat<<EOF > "$KRB5_KDC_PROFILE"
-+[realms]
-+    EXAMPLE.ORG = {
-+        database_name = $gssdir/principal
-+        key_stash_file = $gssdir/stash
-+        kdc_listen = $kdc_hostname:$kdc_port
-+        kdc_tcp_listen = $kdc_hostname:$kdc_port
-+    }
-+[logging]
-+    kdc = FILE:$gssdir/kdc.log
-+    debug = true
-+EOF
-+
-+cat<<EOF > "$KRB5_CONFIG"
-+[libdefaults]
-+    default_realm = EXAMPLE.ORG
-+[realms]
-+    EXAMPLE.ORG = {
-+        kdc = $kdc_hostname:$kdc_port
-+    }
-+EOF
-+
-+# Back up the default sshd_config
-+cp "$OBJ/sshd_config" "$OBJ/sshd_config.orig"
-+
-+setup_sshd() {
-+    kex_alg="$1"
-+
-+    cp "$OBJ/sshd_config.orig" "$OBJ/sshd_config"
-+
-+    cat<<EOF >> "$OBJ/sshd_config"
-+PubkeyAuthentication no
-+PasswordAuthentication no
-+GSSAPIAuthentication yes
-+GSSAPIKeyExchange yes
-+GSSAPIKexAlgorithms $kex_alg
-+GSSAPIStrictAcceptorCheck no
-+EOF
-+
-+    test_ssh_sshd_env_backup="$TEST_SSH_SSHD_ENV"
-+    TEST_SSH_SSHD_ENV="$TEST_SSH_SSHD_ENV                  \
-+                       LD_PRELOAD=$nss_wrapper             \
-+                       NSS_WRAPPER_HOSTS=$hosts            \
-+                       NSS_WRAPPER_HOSTNAME=$sshd_hostname \
-+                       KRB5_CONFIG=$KRB5_CONFIG            \
-+                       KRB5_KDC_PROFILE=$KRB5_KDC_PROFILE  \
-+                       KRB5CCNAME=$KRB5CCNAME              \
-+                       KRB5_KTNAME=$KRB5_KTNAME            \
-+                       KRB5RCACHETYPE=$KRB5RCACHETYPE"
-+    start_sshd
-+}
-+
-+teardown_sshd() {
-+    TEST_SSH_SSHD_ENV="$test_ssh_sshd_env_backup"
-+    stop_sshd
-+}
-+
-+setup_kdc() {
-+    kdb5_util create -P "foo" -s
-+    krb5kdc -w 1 -P "$kdc_pidfile"
-+    i=0;
-+    while [ ! -f "$kdc_pidfile" -a $i -lt 10 ]; do
-+        i=$((i + 1))
-+        sleep 1
-+    done
-+    test -f "$kdc_pidfile" || fatal "KDC failed to start"
-+}
-+
-+teardown_kdc() {
-+    kill "$(cat "$kdc_pidfile")"
-+    kdestroy
-+    rm -f "$KRB5_KTNAME" "$kdc_pidfile"
-+    kdb5_util destroy -f
-+}
-+
-+setup_nss_emulation() {
-+    export LD_PRELOAD="$nss_wrapper"
-+    export NSS_WRAPPER_HOSTS="$hosts"
-+}
-+
-+teardown_nss_emulation() {
-+    unset LD_PRELOAD
-+    unset NSS_WRAPPER_HOSTS
-+}
-+
-+# GSS kex family prefixes to test.
-+# GSSAPIKexAlgorithms accepts these prefixes; the full algorithm names
-+# (prefix + Base64(MD5(OID))) are constructed during negotiation.
-+gss_kex_families="
-+    gss-group14-sha256-
-+    gss-group16-sha512-
-+    gss-nistp256-sha256-
-+    gss-curve25519-sha256-
-+    gss-group14-sha1-
-+    gss-gex-sha1-
-+"
-+
-+# Positive tests: each GSS kex algorithm connects successfully
-+for kex_family in $gss_kex_families; do
-+    # Check if the algorithm family is recognized by ssh
-+    if ! ${SSH} -G -F "$OBJ/ssh_config" \
-+        -o "GSSAPIKeyExchange yes" \
-+        -o "GSSAPIKexAlgorithms $kex_family" \
-+        "$client@$sshd_hostname" >/dev/null 2>&1; then
-+        verbose "gss kex $kex_family not supported, skipping"
-+        continue
-+    fi
-+
-+    verbose "gss kex $kex_family"
-+
-+    setup_nss_emulation
-+    setup_kdc
-+
-+    kadmin.local add_principal -randkey "host/$sshd_hostname"
-+    kadmin.local ktadd "host/$sshd_hostname"
-+    kadmin.local add_principal -pw "foo" "$client"
-+    echo "foo" | kinit "$client"
-+
-+    setup_sshd "$kex_family"
-+
-+    ${SSH} -F "$OBJ/ssh_config" \
-+        -o "GSSAPIAuthentication yes" \
-+        -o "GSSAPIKeyExchange yes" \
-+        -o "GSSAPIKexAlgorithms $kex_family" \
-+        -o "GSSAPIDelegateCredentials no" \
-+        "$client@$sshd_hostname" true
-+    status=$?
-+
-+    teardown_sshd
-+    teardown_kdc
-+    teardown_nss_emulation
-+
-+    if [ $status -ne 0 ]; then
-+        fail "gss kex $kex_family failed"
-+    fi
-+done
-+
-+# Negative test: connection must fail when keytab is missing
-+verbose "gss kex negative test: no keytab"
-+kex_neg="gss-group14-sha256-"
-+
-+if ${SSH} -G -F "$OBJ/ssh_config" \
-+    -o "GSSAPIKeyExchange yes" \
-+    -o "GSSAPIKexAlgorithms $kex_neg" \
-+    "$client@$sshd_hostname" >/dev/null 2>&1; then
-+
-+    setup_nss_emulation
-+    setup_kdc
-+
-+    # Create host principal but do NOT export to keytab
-+    kadmin.local add_principal -randkey "host/$sshd_hostname"
-+    kadmin.local add_principal -pw "foo" "$client"
-+    echo "foo" | kinit "$client"
-+
-+    setup_sshd "$kex_neg"
-+
-+    ${SSH} -F "$OBJ/ssh_config" \
-+        -o "GSSAPIAuthentication yes" \
-+        -o "GSSAPIKeyExchange yes" \
-+        -o "GSSAPIKexAlgorithms $kex_neg" \
-+        -o "GSSAPIDelegateCredentials no" \
-+        "$client@$sshd_hostname" true
-+    status=$?
-+
-+    teardown_sshd
-+    teardown_kdc
-+    teardown_nss_emulation
-+
-+    if [ $status -eq 0 ]; then
-+        fail "gss kex succeeded without keytab"
-+    fi
-+fi
-+
-+unset KRB5CCNAME
-+unset KRB5_CONFIG
-+unset KRB5_KDC_PROFILE
-+unset KRB5_KTNAME
-+unset KRB5RCACHETYPE
-+rm -rf "$gssdir"

diff --git a/1000-openssh-6.7p1-coverity.patch b/1000-openssh-6.7p1-coverity.patch
index a855571..648c42c 100644
--- a/1000-openssh-6.7p1-coverity.patch
+++ b/1000-openssh-6.7p1-coverity.patch
@@ -1,4 +1,4 @@
-From d0225bcdbc0d783df878ccfb5128bdcb787d82ed Mon Sep 17 00:00:00 2001
+From 20271efdf2801f1ddcc308a81db131548872b056 Mon Sep 17 00:00:00 2001
 From: Dmitry Belyavskiy <beldmit@gmail.com>
 Date: Fri, 12 Dec 2025 14:25:41 +0100
 Subject: [PATCH 54/54] openssh-6.7p1-coverity
@@ -98,7 +98,7 @@ index 7499aa975..2807cad4a 100644
  	    MIN_SIZEOF(ut->ut_host, li->hostname));
  # endif
 diff --git a/misc.c b/misc.c
-index e96fa8d61..556c112c3 100644
+index c0e16437a..f5275d108 100644
 --- a/misc.c
 +++ b/misc.c
 @@ -1620,6 +1620,8 @@ sanitise_stdfd(void)
@@ -110,7 +110,7 @@ index e96fa8d61..556c112c3 100644
  }
  
  char *
-@@ -2821,6 +2823,7 @@ stdfd_devnull(int do_stdin, int do_stdout, int do_stderr)
+@@ -2823,6 +2825,7 @@ stdfd_devnull(int do_stdin, int do_stdout, int do_stderr)
  	}
  	if (devnull > STDERR_FILENO)
  		close(devnull);
@@ -119,7 +119,7 @@ index e96fa8d61..556c112c3 100644
  }
  
 diff --git a/monitor.c b/monitor.c
-index 36db1afee..3b60059a9 100644
+index 63180e2a9..be55a32f3 100644
 --- a/monitor.c
 +++ b/monitor.c
 @@ -410,7 +410,7 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
@@ -131,7 +131,7 @@ index 36db1afee..3b60059a9 100644
  		;
  
  	/* Wait for the child's exit status */
-@@ -1855,7 +1855,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
+@@ -1829,7 +1829,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
  	s->ptymaster = s->ptyfd;
  
  	debug3_f("tty %s ptyfd %d", s->tty, s->ttyfd);
@@ -185,7 +185,7 @@ index 26bdc3e08..8e2939b95 100644
  		FD_CLR(notify_pipe[0], readset);
  	}
 diff --git a/readconf.c b/readconf.c
-index 058e53cf1..14f149cc1 100644
+index e971ca5c4..4baeb108d 100644
 --- a/readconf.c
 +++ b/readconf.c
 @@ -2164,6 +2164,7 @@ parse_pubkey_algos:
@@ -197,10 +197,10 @@ index 058e53cf1..14f149cc1 100644
  			}
  			free(arg2);
 diff --git a/servconf.c b/servconf.c
-index e05a31584..3ffa49c3a 100644
+index ab7327a2a..8480e9321 100644
 --- a/servconf.c
 +++ b/servconf.c
-@@ -2382,8 +2382,9 @@ process_server_config_line_depth(ServerOptions *options, char *line,
+@@ -2121,8 +2121,9 @@ process_server_config_line_depth(ServerOptions *options, char *line,
  		if (*activep && *charptr == NULL) {
  			*charptr = tilde_expand_filename(arg, getuid());
  			/* increase optional counter */
@@ -213,7 +213,7 @@ index e05a31584..3ffa49c3a 100644
  		break;
  
 diff --git a/serverloop.c b/serverloop.c
-index 1087a6c7d..b65c9dd66 100644
+index e50985e90..de2dfeef9 100644
 --- a/serverloop.c
 +++ b/serverloop.c
 @@ -536,7 +536,7 @@ server_request_tun(struct ssh *ssh)
@@ -226,5 +226,5 @@ index 1087a6c7d..b65c9dd66 100644
  		    auth_opts->force_tun_device != (int)tun)
  			goto done;
 -- 
-2.53.0
+2.55.0
 

diff --git a/openssh.spec b/openssh.spec
index 13ba36d..053d924 100644
--- a/openssh.spec
+++ b/openssh.spec
@@ -34,12 +34,12 @@
 # rpm -ba|--rebuild --define 'no_gtk3 1'
 %{?no_gtk3:%global gtk3 0}
 
-%global openssh_ver 10.3p1
+%global openssh_ver 10.4p1
 
 Summary: An open source implementation of SSH protocol version 2
 Name: openssh
 Version: %{openssh_ver}
-Release: 8%{?dist}
+Release: 1%{?dist}
 URL: http://www.openssh.com/portable.html
 Source0: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz
 Source1: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz.asc
@@ -61,59 +61,59 @@ Source21: ssh-host-keys-migration.service
 Source22: parallel_test.sh
 Source23: parallel_test.Makefile
 
-Patch0001: 0001-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
-Patch0002: 0002-Implement-SELinux-environment-variable-setup-for-sub.patch
-Patch0003: 0003-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
-Patch0004: 0004-openssh-6.6p1-keycat.patch
-Patch0005: 0005-openssh-6.6p1-allow-ip-opts.patch
-Patch0006: 0006-openssh-5.9p1-ipv6man.patch
-Patch0007: 0007-openssh-5.8p2-sigpipe.patch
-Patch0009: 0009-openssh-5.1p1-askpass-progress.patch
-Patch0010: 0010-openssh-4.3p2-askpass-grab-info.patch
-Patch0011: 0011-openssh-8.7p1-redhat.patch
-Patch0012: 0012-openssh-7.8p1-UsePAM-warning.patch
-Patch0013: 0013-openssh-9.6p1-gssapi-keyex.patch
-Patch0014: 0014-openssh-6.6p1-force_krb.patch
-Patch0015: 0015-openssh-7.7p1-gssapi-new-unique.patch
-Patch0016: 0016-openssh-7.2p2-k5login_directory.patch
-Patch0017: 0017-openssh-6.6p1-kuserok.patch
-Patch0018: 0018-openssh-6.4p1-fromto-remote.patch
-Patch0019: 0019-openssh-6.6.1p1-log-in-chroot.patch
-Patch0020: 0020-openssh-6.6.1p1-scp-non-existing-directory.patch
-Patch0021: 0021-openssh-6.6p1-GSSAPIEnablek5users.patch
-Patch0022: 0022-openssh-6.8p1-sshdT-output.patch
-Patch0023: 0023-openssh-6.7p1-sftp-force-permission.patch
-Patch0024: 0024-openssh-7.2p2-s390-closefrom.patch
-Patch0025: 0025-openssh-7.5p1-sandbox.patch
-Patch0026: 0026-openssh-7.8p1-scp-ipv6.patch
-Patch0027: 0027-openssh-8.0p1-crypto-policies.patch
-Patch0028: 0028-openssh-8.0p1-openssl-kdf.patch
-Patch0029: 0029-openssh-8.2p1-visibility.patch
-Patch0030: 0030-openssh-8.2p1-x11-without-ipv6.patch
-Patch0031: 0031-openssh-8.0p1-preserve-pam-errors.patch
-Patch0032: 0032-openssh-8.7p1-scp-kill-switch.patch
-Patch0033: 0033-openssh-8.7p1-recursive-scp.patch
-Patch0034: 0034-openssh-8.7p1-ibmca.patch
-Patch0035: 0035-openssh-7.6p1-audit.patch
-Patch0036: 0036-openssh-7.1p2-audit-race-condition.patch
-Patch0037: 0037-openssh-9.0p1-audit-log.patch
-Patch0038: 0038-openssh-7.7p1-fips.patch
-Patch0039: 0039-openssh-8.7p1-negotiate-supported-algs.patch
-Patch0040: 0040-openssh-9.0p1-evp-fips-kex.patch
-Patch0041: 0041-openssh-8.7p1-nohostsha1proof.patch
-Patch0042: 0042-openssh-9.9p1-separate-keysign.patch
-Patch0043: 0043-openssh-9.9p1-openssl-mlkem.patch
-Patch0044: 0044-openssh-9.9p2-error_processing.patch
-Patch0045: 0045-Provide-better-error-for-non-supported-private-keys.patch
-Patch0046: 0046-Ignore-bad-hostkeys-in-known_hosts-file.patch
-Patch0047: 0047-support-authentication-indicators-in-GSSAPI.patch
-Patch0048: 0048-NIST-curves-hybrid-KEX-implementation.patch
-Patch0049: 0049-openssh-7.3p1-x11-max-displays.patch
-Patch0050: 0050-Fix-ssh-pkcs11-client-helper-termination.patch
-Patch0051: 0051-openssh-10.2p1-pam-auth.patch
-Patch0052: 0052-gssapi-s4u.patch
-Patch0053: 0053-openssh-10.2p1-pkcs11-uri.patch
-Patch0054: 0054-gssapi-tests.patch
+Patch0001: 0001-upstream-fix-GSSAPI-option-names-that-I-somehow-scre.patch
+Patch0002: 0002-Add-SELinux-role-and-MLS-Multi-Level-Security-suppor.patch
+Patch0003: 0003-Implement-SELinux-environment-variable-setup-for-sub.patch
+Patch0004: 0004-Pass-inetd-flags-and-auth-context-to-subprocess-call.patch
+Patch0005: 0005-openssh-6.6p1-keycat.patch
+Patch0006: 0006-openssh-6.6p1-allow-ip-opts.patch
+Patch0007: 0007-openssh-5.9p1-ipv6man.patch
+Patch0009: 0008-openssh-5.8p2-sigpipe.patch
+Patch0010: 0009-openssh-5.1p1-askpass-progress.patch
+Patch0011: 0010-openssh-4.3p2-askpass-grab-info.patch
+Patch0012: 0011-openssh-8.7p1-redhat.patch
+Patch0013: 0012-openssh-7.8p1-UsePAM-warning.patch
+Patch0014: 0013-openssh-9.6p1-gssapi-keyex.patch
+Patch0015: 0014-openssh-6.6p1-force_krb.patch
+Patch0016: 0015-openssh-7.7p1-gssapi-new-unique.patch
+Patch0017: 0016-openssh-7.2p2-k5login_directory.patch
+Patch0018: 0017-openssh-6.6p1-kuserok.patch
+Patch0019: 0018-openssh-6.4p1-fromto-remote.patch
+Patch0020: 0019-openssh-6.6.1p1-log-in-chroot.patch
+Patch0021: 0020-openssh-6.6.1p1-scp-non-existing-directory.patch
+Patch0022: 0021-openssh-6.6p1-GSSAPIEnablek5users.patch
+Patch0023: 0022-openssh-6.8p1-sshdT-output.patch
+Patch0024: 0023-openssh-6.7p1-sftp-force-permission.patch
+Patch0025: 0024-openssh-7.2p2-s390-closefrom.patch
+Patch0026: 0025-openssh-7.5p1-sandbox.patch
+Patch0027: 0026-openssh-7.8p1-scp-ipv6.patch
+Patch0028: 0027-openssh-8.0p1-crypto-policies.patch
+Patch0029: 0028-openssh-8.0p1-openssl-kdf.patch
+Patch0030: 0029-openssh-8.2p1-visibility.patch
+Patch0031: 0030-openssh-8.2p1-x11-without-ipv6.patch
+Patch0032: 0031-openssh-8.0p1-preserve-pam-errors.patch
+Patch0033: 0032-openssh-8.7p1-scp-kill-switch.patch
+Patch0034: 0033-openssh-8.7p1-recursive-scp.patch
+Patch0035: 0034-openssh-8.7p1-ibmca.patch
+Patch0036: 0035-openssh-7.6p1-audit.patch
+Patch0037: 0036-openssh-7.1p2-audit-race-condition.patch
+Patch0038: 0037-openssh-9.0p1-audit-log.patch
+Patch0039: 0038-openssh-7.7p1-fips.patch
+Patch0040: 0039-openssh-8.7p1-negotiate-supported-algs.patch
+Patch0041: 0040-openssh-9.0p1-evp-fips-kex.patch
+Patch0042: 0041-openssh-8.7p1-nohostsha1proof.patch
+Patch0043: 0042-openssh-9.9p1-separate-keysign.patch
+Patch0044: 0043-openssh-9.9p1-openssl-mlkem.patch
+Patch0045: 0044-openssh-9.9p2-error_processing.patch
+Patch0046: 0045-Ignore-bad-hostkeys-in-known_hosts-file.patch
+Patch0047: 0046-support-authentication-indicators-in-GSSAPI.patch
+Patch0048: 0047-NIST-curves-hybrid-KEX-implementation.patch
+Patch0049: 0048-openssh-7.3p1-x11-max-displays.patch
+Patch0050: 0049-Fix-ssh-pkcs11-client-helper-termination.patch
+Patch0051: 0050-openssh-10.2p1-pam-auth.patch
+Patch0052: 0051-gssapi-s4u.patch
+Patch0053: 0052-openssh-10.2p1-pkcs11-uri.patch
+Patch0054: 0053-gssapi-tests.patch
 Patch1000: 1000-openssh-6.7p1-coverity.patch
 
 License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND snprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant
@@ -513,6 +513,9 @@ test -f %{sysconfig_anaconda} && \
 %attr(0755,root,root) %{_libdir}/sshtest/sk-dummy.so
 
 %changelog
+* Wed Jul 08 2026 Dmitry Belyavskiy <dbelyavs@redhat.com> - 10.4p1-1
+- Rebasing OpenSSH to 10.4p1
+
 * Wed Jul 08 2026 Dmitry Belyavskiy <dbelyavs@redhat.com> - 10.3p1-8
 - Fix a typo in crypto-policies documentation patch
   Resolves: rhbz#2241564

diff --git a/sources b/sources
index 5d89229..256fec5 100644
--- a/sources
+++ b/sources
@@ -1,3 +1,3 @@
-SHA512 (openssh-10.3p1.tar.gz) = cb2bd67086491c25e305879b924c3dfa8236502a60c7f250b2fd17d2d9a79ebfc2e40b2f43e42dcf598cc510996e00cc03df9b8e38f34bc2dc71a3d4ff3788fa
-SHA512 (openssh-10.3p1.tar.gz.asc) = 2c8afbe57f6712f159aa3a160b6ffc43f945a98ccd8e151fa6a047ea30b376e5e1cac844a678a7899da80704b3d23feda612ffada1ee003f4c7fb8291f484600
+SHA512 (openssh-10.4p1.tar.gz) = c49600022a3a3f0f0ba4284072cccbe1088030cd175ea166e08251224712731f97cb1a3f039d19f71714c537ab88b177602be0932bc35a26f3227dc09c43f37c
+SHA512 (openssh-10.4p1.tar.gz.asc) = 604b8203088d71bee3a93ea644201f82eb1f049b08cf0b57b9f5dbcb9a9bae206283a30af15182e42e59ea63b8e1c9941c8b981a1d026b6a0f49b11ccd7007c4
 SHA512 (gpgkey-736060BA.gpg) = df44f3fdbcd1d596705348c7f5aed3f738c5f626a55955e0642f7c6c082995cf36a1b1891bb41b8715cb2aff34fef1c877e0eff0d3507dd00a055ba695757a21

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-07-09  9:41 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-09  9:41 [rpms/openssh] rawhide: Rebase to OpenSSH 10.4p1 Dmitry Belyavskiy

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox