--- ./cryptlib.h.orig	2024-10-31 00:50:22.829934000 -0400
+++ ./cryptlib.h	2024-10-31 00:50:23.029627000 -0400
@@ -1236,6 +1236,7 @@
 	CRYPT_SESSINFO_PASSWORD,		/* Password */
 	CRYPT_SESSINFO_AUTHTOKEN,		/* Authentication token, e.g. TOTP */
 	CRYPT_SESSINFO_PRIVATEKEY,		/* Server/client private key */
+	CRYPT_SESSINFO_PUBLICKEY,		/* Other sides public key */
 	CRYPT_SESSINFO_KEYSET,			/* Certificate store */
 	CRYPT_SESSINFO_AUTHRESPONSE,	/* Session authorisation OK */
 
--- ./session/ssh2_authsvr.c.orig	2023-02-26 03:33:26.000000000 -0500
+++ ./session/ssh2_authsvr.c	2024-10-31 00:50:23.044734000 -0400
@@ -396,7 +396,7 @@
 	/* If we've already seen a standard authentication method then the new 
 	   method must be the same */
 	if( sshInfo->authType != authType )
-		return( CRYPT_ERROR_INVALID );
+		sshInfo->authType = authType;
 
 	return( CRYPT_OK );
 	}
@@ -511,66 +511,69 @@
 	   assume that any certificate present will be a cryptlib-generated one 
 	   used as a bit-bagging mechanism to get the key into a database, and
 	   therefore that sKID == hash( subjectPublicKey ) */
-	setMessageData( &msgData, keyID, CRYPT_MAX_HASHSIZE );
-	status = krnlSendMessage( sessionInfoPtr->iKeyexAuthContext, 
-							  IMESSAGE_GETATTRIBUTE_S, &msgData, 
-							  CRYPT_IATTRIBUTE_KEYID );
-	if( cryptStatusOK( status ) )
+	if (sessionInfoPtr->cryptKeyset != CRYPT_ERROR )
 		{
-		setMessageKeymgmtInfo( &getkeyInfo, CRYPT_IKEYID_KEYID, 
-							   msgData.data, msgData.length, NULL, 0, 
-							   KEYMGMT_FLAG_NONE );
-		status = krnlSendMessage( sessionInfoPtr->cryptKeyset, 
-								  IMESSAGE_KEY_GETKEY, &getkeyInfo, 
-								  KEYMGMT_ITEM_PUBLICKEY );
-		}
-	if( cryptStatusError( status ) )
-		{
+		setMessageData( &msgData, keyID, CRYPT_MAX_HASHSIZE );
+		status = krnlSendMessage( sessionInfoPtr->iKeyexAuthContext, 
+								  IMESSAGE_GETATTRIBUTE_S, &msgData, 
+								  CRYPT_IATTRIBUTE_KEYID );
+		if( cryptStatusOK( status ) )
+			{
+			setMessageKeymgmtInfo( &getkeyInfo, CRYPT_IKEYID_KEYID, 
+								   msgData.data, msgData.length, NULL, 0, 
+								   KEYMGMT_FLAG_NONE );
+			status = krnlSendMessage( sessionInfoPtr->cryptKeyset, 
+									  IMESSAGE_KEY_GETKEY, &getkeyInfo, 
+									  KEYMGMT_ITEM_PUBLICKEY );
+			}
+		if( cryptStatusError( status ) )
+			{
 #ifdef USE_ERRMSGS
-		char keyIDText[ CRYPT_MAX_TEXTSIZE + 8 ];
+			char keyIDText[ CRYPT_MAX_TEXTSIZE + 8 ];
 #endif /* USE_ERRMSGS */
 
-		formatHexData( keyIDText, CRYPT_MAX_TEXTSIZE, keyID, 
-					   msgData.length );
-		retExt( CRYPT_ERROR_PERMISSION,
-				( CRYPT_ERROR_PERMISSION, SESSION_ERRINFO, 
-				  "Client public key with ID '%s' is not trusted for "
-				  "authentication purposes", keyIDText ) );
-		}
+			formatHexData( keyIDText, CRYPT_MAX_TEXTSIZE, keyID, 
+						   msgData.length );
+			retExt( CRYPT_ERROR_PERMISSION,
+					( CRYPT_ERROR_PERMISSION, SESSION_ERRINFO, 
+					  "Client public key with ID '%s' is not trusted for "
+					  "authentication purposes", keyIDText ) );
+			}
 
-	/* Check that the name in the certificate matches the supplied user 
-	   name */
-	setMessageData( &msgData, holderName, CRYPT_MAX_TEXTSIZE );
-	status = krnlSendMessage( getkeyInfo.cryptHandle, IMESSAGE_GETATTRIBUTE_S,
+		/* Check that the name in the certificate matches the supplied user 
+		   name */
+		setMessageData( &msgData, holderName, CRYPT_MAX_TEXTSIZE );
+		status = krnlSendMessage( getkeyInfo.cryptHandle, IMESSAGE_GETATTRIBUTE_S,
 							  &msgData, CRYPT_IATTRIBUTE_HOLDERNAME );
-	krnlSendNotifier( getkeyInfo.cryptHandle, IMESSAGE_DESTROY );
-	if( cryptStatusOK( status ) )
-		{
-		holderNameLen = msgData.length;
-		if( userNameLength != holderNameLen || \
-			compareDataConstTime( userName, holderName, 
-								  userNameLength ) != TRUE )
-			status = CRYPT_ERROR_INVALID;
-		}
-	else
-		{
-		memcpy( holderName, "<Unknown>", 9 );
-		holderNameLen = 9;
-		}
-	if( cryptStatusError( status ) )
-		{
-		BYTE userNameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
+		krnlSendNotifier( getkeyInfo.cryptHandle, IMESSAGE_DESTROY );
+		if( cryptStatusOK( status ) )
+			{
+			holderNameLen = msgData.length;
+			if( userNameLength != holderNameLen || \
+				compareDataConstTime( userName, holderName, 
+									  userNameLength ) != TRUE )
+				status = CRYPT_ERROR_INVALID;
+			}
+		else
+			{
+			memcpy( holderName, "<Unknown>", 9 );
+			holderNameLen = 9;
+			}
+		if( cryptStatusError( status ) )
+			{
+			BYTE userNameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
 
-		REQUIRES( rangeCheck( userNameLength, 1, CRYPT_MAX_TEXTSIZE ) );
-		memcpy( userNameBuffer, userName, userNameLength );
-		retExt( CRYPT_ERROR_INVALID,
-				( CRYPT_ERROR_INVALID, SESSION_ERRINFO, 
-				  "Client public key name '%s' doesn't match supplied user "
-				  "name '%s'", 
-				  sanitiseString( holderName, CRYPT_MAX_TEXTSIZE, 
-								  holderNameLen ),
-				  sanitiseString( userNameBuffer, CRYPT_MAX_TEXTSIZE, 
-								  userNameLength ) ) );
+			REQUIRES( rangeCheck( userNameLength, 1, CRYPT_MAX_TEXTSIZE ) );
+			memcpy( userNameBuffer, userName, userNameLength );
+			retExt( CRYPT_ERROR_INVALID,
+					( CRYPT_ERROR_INVALID, SESSION_ERRINFO, 
+					  "Client public key name '%s' doesn't match supplied user "
+					  "name '%s'", 
+					  sanitiseString( holderName, CRYPT_MAX_TEXTSIZE, 
+									  holderNameLen ),
+					  sanitiseString( userNameBuffer, CRYPT_MAX_TEXTSIZE, 
+									  userNameLength ) ) );
+			}
 		}
 
 	/* Get a pointer to the portion of the packet that gets signed */
@@ -662,8 +665,8 @@
 							IN_BOOL const BOOLEAN initialAuth )
 	{
 	STREAM stream;
-	const BOOLEAN allowPubkeyAuth = \
-			( sessionInfoPtr->cryptKeyset != CRYPT_ERROR ) ? TRUE : FALSE;
+	// Always allow public key auth...
+	const BOOLEAN allowPubkeyAuth = TRUE;
 	const AUTHTYPE_INFO *authTypeInfoTblPtr = allowPubkeyAuth ? \
 			authTypeInfoTbl : authTypeInfoPasswordTbl;
 	const int authTypeInfoTblSize = allowPubkeyAuth ? \
@@ -1098,23 +1101,67 @@
 			}
 		CFI_CHECK_UPDATE( "checkPublicKeySig" );
 
-		/* The user has successfully authenticated, let the client know and 
-		   indicate this through a failsafe two-value return status (see the 
-		   comment for processFixedAuth()/processServerAuth() for details) */
-		status = sendResponseSuccess( sessionInfoPtr );
-		if( cryptStatusError( status ) )
-			return( status );
-		*userAuthInfo = USERAUTH_SUCCESS;
-		CFI_CHECK_UPDATE( "sendResponseSuccess" );
+		if (sessionInfoPtr->cryptKeyset != CRYPT_ERROR )
+			{
+			/* The user has successfully authenticated, let the client know and 
+			   indicate this through a failsafe two-value return status (see the 
+			   comment for processFixedAuth()/processServerAuth() for details) */
+			status = sendResponseSuccess( sessionInfoPtr );
+			if( cryptStatusError( status ) )
+				return( status );
+			CFI_CHECK_UPDATE( "sendResponseSuccess" );
 
-		ENSURES( CFI_CHECK_SEQUENCE_8( "readAuthPacketSSH2", 
-									   "checkAuthPacketSSH2", 
-									   "checkQueryValidity", "readAuthInfo", 
-									   "findSessionInfoEx", 
-									   "SSH_AUTHTYPE_QUERY",
-									   "checkPublicKeySig", 
-									   "sendResponseSuccess" ) );
-		return( CRYPT_OK );
+			ENSURES( CFI_CHECK_SEQUENCE_8( "readAuthPacketSSH2", 
+										   "checkAuthPacketSSH2", 
+										   "checkQueryValidity", "readAuthInfo", 
+										   "findSessionInfoEx", 
+										   "SSH_AUTHTYPE_QUERY",
+										   "checkPublicKeySig", 
+										   "sendResponseSuccess" ) );
+			*userAuthInfo = USERAUTH_SUCCESS;
+			return( CRYPT_OK );
+			}
+		else
+			{
+			/* There are no pre-set credentials present to match against, record the 
+			   public key for the caller to check, making it an ephemeral attribute 
+			   since the client could try and re-enter it on a subsequent iteration 
+			   if we tell them that it's incorrect. */
+			MESSAGE_DATA msgData;
+			BYTE keyBuffer[2056];
+			setMessageData( &msgData, keyBuffer, sizeof(keyBuffer) - 8);
+			status = krnlSendMessage( sessionInfoPtr->iKeyexAuthContext, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_IATTRIBUTE_KEY_SSH );
+
+			if( cryptStatusError( status ) )
+				{
+				retExt( status,
+						( status, SESSION_ERRINFO, 
+						  "Error getting public key for user '%s'",
+						  sanitiseString( userNameBuffer, CRYPT_MAX_TEXTSIZE,
+										  userNameLength ) ) );
+				}
+			status = updateSessionInfo( sessionInfoPtr, CRYPT_SESSINFO_PUBLICKEY,
+										msgData.data, msgData.length,
+										sizeof(keyBuffer) - 8, ATTR_FLAG_EPHEMERAL );
+			if( cryptStatusError( status ) )
+				{
+				retExt( status,
+						( status, SESSION_ERRINFO, 
+						  "Error recording public key for user '%s'",
+						  sanitiseString( userNameBuffer, CRYPT_MAX_TEXTSIZE,
+										  userNameLength ) ) );
+				}
+			CFI_CHECK_UPDATE( "updateSessionInfo" );
+			ENSURES( CFI_CHECK_SEQUENCE_8( "readAuthPacketSSH2", 
+										   "checkAuthPacketSSH2", 
+										   "checkQueryValidity", "readAuthInfo", 
+										   "findSessionInfoEx", 
+										   "SSH_AUTHTYPE_QUERY",
+										   "checkPublicKeySig",
+										   "updateSessionInfo" ) );
+			*userAuthInfo = USERAUTH_CALLERCHECK;
+			return( OK_SPECIAL );
+			}
 		}
 	sMemDisconnect( &stream );
 	CFI_CHECK_UPDATE( "SSH_AUTHTYPE_PUBKEY" );
--- ./session/sess_attr.c.orig	2024-10-31 00:50:22.601581000 -0400
+++ ./session/sess_attr.c	2024-10-31 00:50:23.048496000 -0400
@@ -913,6 +913,7 @@
 		case CRYPT_SESSINFO_SERVER_FINGERPRINT_SHA1:
 		case CRYPT_SESSINFO_SERVER_NAME:
 		case CRYPT_SESSINFO_CLIENT_NAME:
+		case CRYPT_SESSINFO_PUBLICKEY:
 			attributeListPtr = findSessionInfo( sessionInfoPtr, attribute );
 			if( attributeListPtr == NULL )
 				return( exitErrorNotInited( sessionInfoPtr, attribute ) );
--- ./session/sess_iattr.c.orig	2023-06-06 00:20:48.000000000 -0400
+++ ./session/sess_iattr.c	2024-10-31 00:50:23.040192000 -0400
@@ -327,7 +327,8 @@
 			attributeListPtr = DATAPTR_GET( attributeListPtr->next );
 			if( attributeListPtr == NULL || \
 				( attributeListPtr->attributeID != CRYPT_SESSINFO_PASSWORD && \
-				  attributeListPtr->attributeID != CRYPT_SESSINFO_AUTHTOKEN ) )
+				  attributeListPtr->attributeID != CRYPT_SESSINFO_AUTHTOKEN && \
+				  attributeListPtr->attributeID != CRYPT_SESSINFO_PUBLICKEY ) )
 				{
 				/* We report the missing attribute as a password, which is 
 				   more likely and more understandable than a missing 
--- ./kernel/attr_acl.c.orig	2024-10-31 00:50:22.675666000 -0400
+++ ./kernel/attr_acl.c	2024-10-31 00:50:23.034531000 -0400
@@ -3770,6 +3770,15 @@
 		MKPERM_SESSIONS( xWx_xWx ),
 		ROUTE( OBJECT_TYPE_SESSION ),
 		subACL_SessinfoPrivatekey ),
+	MKACL_S(	/* Other side public key */
+		/* We can read this attribute in the low state because we might be
+		   going back to the caller for confirmation before we transition
+		   into the high state */
+		CRYPT_SESSINFO_PUBLICKEY,
+		ST_NONE, ST_NONE, ST_SESS_SSH_SVR, 
+		MKPERM_SESSIONS( Rxx_Rxx ),
+		ROUTE( OBJECT_TYPE_SESSION ),
+		RANGE( 1, CRYPT_MAX_TEXTSIZE ) ),
 	MKACL_ST(	/* Certificate store/auth.keyset */
 		CRYPT_SESSINFO_KEYSET,
 		ST_NONE, ST_NONE, MK_ST_EXCEPTION( ST_SESS_ANY_SVR, ST_SESS_TSP_SVR ) | \
@@ -4973,8 +4982,8 @@
 	static_assert( CRYPT_CERTINFO_LAST_GENERALNAME == 2115, "Attribute value" );
 	static_assert( CRYPT_CERTINFO_FIRST_EXTENSION == 2200, "Attribute value" );
 	static_assert( CRYPT_CERTINFO_FIRST_CMS == 2500, "Attribute value" );
-	static_assert( CRYPT_SESSINFO_FIRST_SPECIFIC == 6017, "Attribute value" );
-	static_assert( CRYPT_SESSINFO_LAST_SPECIFIC == 6040, "Attribute value" );
+	static_assert( CRYPT_SESSINFO_FIRST_SPECIFIC == 6018, "Attribute value" );
+	static_assert( CRYPT_SESSINFO_LAST_SPECIFIC == 6041, "Attribute value" );
 	static_assert( CRYPT_CERTFORMAT_LAST == 13, "Attribute value" );
 
 	/* Perform a consistency check on the attribute ACLs.  The ACLs are
