public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Dmitry Belyavskiy <dbelyavs@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/openssh] rawhide: Rebase to OpenSSH 10.4p1
Date: Thu, 09 Jul 2026 09:41:04 GMT [thread overview]
Message-ID: <178359006448.1.16876012394490289070.rpms-openssh-c8d70d4d0462@fedoraproject.org> (raw)
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&#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&#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
reply other threads:[~2026-07-09 9:41 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178359006448.1.16876012394490289070.rpms-openssh-c8d70d4d0462@fedoraproject.org \
--to=dbelyavs@redhat.com \
--cc=git-commits@fedoraproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox